| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.myfaces.resource; |
| |
| import java.io.BufferedInputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import javax.faces.FacesException; |
| import javax.faces.application.Resource; |
| import javax.faces.context.FacesContext; |
| import javax.servlet.ServletContext; |
| import org.apache.myfaces.config.MyfacesConfig; |
| |
| /** |
| * ResourceLoader that uses a temporal folder to cache resources, avoiding the problem |
| * described on MYFACES-3586 (Performance improvement in Resource loading - |
| * HIGH CPU inflating bytes in ResourceHandlerImpl.handleResourceRequest). |
| * |
| * @author Leonardo Uribe |
| */ |
| public class TempDirFileCacheContractResourceLoader extends ContractResourceLoaderWrapper |
| { |
| |
| public final static String TEMP_FILES_LOCK_MAP = "oam.rh.con.TEMP_FILES_LOCK_MAP"; |
| |
| /** |
| * Subdir of the ServletContext tmp dir to store temporal resources. |
| */ |
| private static final String TEMP_FOLDER_BASE_DIR = "oam-rh-cache/"; |
| |
| /** |
| * Suffix for temporal files. |
| */ |
| private static final String TEMP_FILE_SUFFIX = ".tmp"; |
| |
| private final ContractResourceLoader delegate; |
| |
| private volatile File _tempDir; |
| |
| private int _resourceBufferSize = -1; |
| |
| public TempDirFileCacheContractResourceLoader(ContractResourceLoader delegate) |
| { |
| this.delegate = delegate; |
| initialize(); |
| } |
| |
| protected void initialize() |
| { |
| //Get startup FacesContext |
| FacesContext facesContext = FacesContext.getCurrentInstance(); |
| |
| //1. Create temporal directory for temporal resources |
| Map<String, Object> applicationMap = facesContext.getExternalContext().getApplicationMap(); |
| File tempdir = (File) applicationMap.get(ServletContext.TEMPDIR); |
| File imagesDir = new File(tempdir, TEMP_FOLDER_BASE_DIR); |
| if (!imagesDir.exists()) |
| { |
| imagesDir.mkdirs(); |
| } |
| else |
| { |
| //Clear the cache |
| deleteDir(imagesDir); |
| imagesDir.mkdirs(); |
| } |
| _tempDir = imagesDir; |
| |
| //2. Create map for register temporal resources |
| Map<String, FileProducer> temporalFilesLockMap = new ConcurrentHashMap<String, FileProducer>(); |
| facesContext.getExternalContext().getApplicationMap().put(TEMP_FILES_LOCK_MAP, temporalFilesLockMap); |
| } |
| |
| private static boolean deleteDir(File dir) |
| { |
| if (dir.isDirectory()) |
| { |
| String[] children = dir.list(); |
| for (int i = 0; i < children.length; i++) |
| { |
| boolean success = deleteDir(new File(dir, children[i])); |
| if (!success) |
| { |
| return false; |
| } |
| } |
| } |
| return dir.delete(); |
| } |
| |
| @Override |
| public URL getResourceURL(ResourceMeta resourceMeta) |
| { |
| FacesContext facesContext = FacesContext.getCurrentInstance(); |
| |
| if (resourceExists(resourceMeta)) |
| { |
| File file = createOrGetTempFile(facesContext, resourceMeta); |
| |
| try |
| { |
| return file.toURL(); |
| } |
| catch (MalformedURLException e) |
| { |
| throw new FacesException(e); |
| } |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| public InputStream getResourceInputStream(ResourceMeta resourceMeta, Resource resource) |
| { |
| FacesContext facesContext = FacesContext.getCurrentInstance(); |
| |
| if (resourceExists(resourceMeta)) |
| { |
| File file = createOrGetTempFile(facesContext, resourceMeta); |
| |
| try |
| { |
| return new BufferedInputStream(new FileInputStream(file)); |
| } |
| catch (FileNotFoundException e) |
| { |
| throw new FacesException(e); |
| } |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| @Override |
| public InputStream getResourceInputStream(ResourceMeta resourceMeta) |
| { |
| return getResourceInputStream(resourceMeta, null); |
| } |
| |
| @Override |
| public boolean resourceExists(ResourceMeta resourceMeta) |
| { |
| return super.resourceExists(resourceMeta); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private File createOrGetTempFile(FacesContext facesContext, ResourceMeta resourceMeta) |
| { |
| String identifier = resourceMeta.getResourceIdentifier()+ '_' +resourceMeta.getContractName(); |
| File file = getTemporalFile(resourceMeta); |
| if (!file.exists()) |
| { |
| Map<String, FileProducer> map = (Map<String, FileProducer>) |
| facesContext.getExternalContext().getApplicationMap().get(TEMP_FILES_LOCK_MAP); |
| |
| FileProducer creator = map.get(identifier); |
| |
| if (creator == null) |
| { |
| synchronized(this) |
| { |
| creator = map.get(identifier); |
| |
| if (creator == null) |
| { |
| creator = new FileProducer(); |
| map.put(identifier, creator); |
| } |
| } |
| } |
| |
| if (!creator.isCreated()) |
| { |
| creator.createFile(facesContext, resourceMeta, file, this); |
| } |
| } |
| return file; |
| } |
| |
| private File getTemporalFile(ResourceMeta resourceMeta) |
| { |
| return new File(_tempDir, |
| resourceMeta.getResourceIdentifier()+ '_' + resourceMeta.getContractName() + TEMP_FILE_SUFFIX); |
| } |
| |
| protected void createTemporalFileVersion(FacesContext facesContext, ResourceMeta resourceMeta, File target) |
| { |
| target.mkdirs(); // ensure necessary directories exist |
| target.delete(); // remove any existing file |
| |
| InputStream inputStream = null; |
| FileOutputStream fileOutputStream; |
| try |
| { |
| inputStream = getWrapped().getResourceInputStream(resourceMeta); |
| fileOutputStream = new FileOutputStream(target); |
| byte[] buffer = new byte[this.getResourceBufferSize()]; |
| |
| pipeBytes(inputStream, fileOutputStream, buffer); |
| } |
| catch (FileNotFoundException e) |
| { |
| throw new FacesException("Unexpected exception while create file:", e); |
| } |
| catch (IOException e) |
| { |
| throw new FacesException("Unexpected exception while create file:", e); |
| } |
| finally |
| { |
| if (inputStream != null) |
| { |
| try |
| { |
| inputStream.close(); |
| } |
| catch (IOException e) |
| { |
| // Ignore |
| } |
| } |
| } |
| } |
| |
| /** |
| * Reads the specified input stream into the provided byte array storage and |
| * writes it to the output stream. |
| */ |
| private static void pipeBytes(InputStream in, OutputStream out, byte[] buffer) throws IOException |
| { |
| int length; |
| |
| while ((length = (in.read(buffer))) >= 0) |
| { |
| out.write(buffer, 0, length); |
| } |
| } |
| |
| public static class FileProducer |
| { |
| public volatile boolean created = false; |
| |
| public FileProducer() |
| { |
| super(); |
| } |
| |
| public boolean isCreated() |
| { |
| return created; |
| } |
| |
| public synchronized void createFile(FacesContext facesContext, |
| ResourceMeta resourceMeta, File file, TempDirFileCacheContractResourceLoader loader) |
| { |
| if (!created) |
| { |
| loader.createTemporalFileVersion(facesContext, resourceMeta, file); |
| created = true; |
| } |
| } |
| } |
| |
| protected int getResourceBufferSize() |
| { |
| if (_resourceBufferSize == -1) |
| { |
| _resourceBufferSize = MyfacesConfig.getCurrentInstance().getResourceBufferSize(); |
| } |
| return _resourceBufferSize; |
| } |
| |
| @Override |
| public ContractResourceLoader getWrapped() |
| { |
| return delegate; |
| } |
| } |