View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.resource;
20  
21  import java.io.BufferedInputStream;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileNotFoundException;
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.OutputStream;
29  import java.net.MalformedURLException;
30  import java.net.URL;
31  import java.util.Map;
32  import java.util.concurrent.ConcurrentHashMap;
33  import javax.faces.FacesException;
34  import javax.faces.application.Resource;
35  import javax.faces.context.FacesContext;
36  import org.apache.myfaces.application.ResourceHandlerImpl;
37  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
38  import org.apache.myfaces.shared.resource.ResourceLoader;
39  import org.apache.myfaces.shared.resource.ResourceLoaderWrapper;
40  import org.apache.myfaces.shared.resource.ResourceMeta;
41  import org.apache.myfaces.shared.util.WebConfigParamUtils;
42  
43  /**
44   * ResourceLoader that uses a temporal folder to cache resources, avoiding the problem
45   * described on  MYFACES-3586 (Performance improvement in Resource loading - 
46   * HIGH CPU inflating bytes in ResourceHandlerImpl.handleResourceRequest).
47   *
48   * @author Leonardo Uribe
49   */
50  public class TempDirFileCacheResourceLoader extends ResourceLoaderWrapper
51  {
52      /**
53       * If this param is set to true (default false), a temporal directory is created and
54       * all files handled by this ResourceLoader are cached there, avoiding the problem
55       * described on MYFACES-3586. (Performance improvement in Resource loading - 
56       * HIGH CPU inflating bytes in ResourceHandlerImpl.handleResourceRequest).
57       */
58      @JSFWebConfigParam(since="2.1.11", expectedValues="true, false", defaultValue="false")
59      public final static String INIT_PARAM_TEMPORAL_RESOURCEHANDLER_CACHE_ENABLED = 
60          "org.apache.myfaces.TEMPORAL_RESOURCEHANDLER_CACHE_ENABLED";
61      public final static boolean INIT_PARAM_TEMPORAL_RESOURCEHANDLER_CACHE_ENABLED_DEFAULT = false;
62      
63      public final static String TEMP_FILES_LOCK_MAP = "oam.rh.TEMP_FILES_LOCK_MAP";
64      
65      /**
66       * Subdir of the ServletContext tmp dir to store temporal resources.
67       */
68      private static final String TEMP_FOLDER_BASE_DIR = "oam-rh-cache/";
69  
70      /**
71       * Suffix for temporal files.
72       */
73      private static final String TEMP_FILE_SUFFIX = ".tmp";
74      
75      private ResourceLoader delegate;
76      
77      private volatile File _tempDir;
78      
79      private int _resourceBufferSize = -1;
80      
81      public TempDirFileCacheResourceLoader(ResourceLoader delegate)
82      {
83          this.delegate = delegate;
84          initialize();
85      }
86      
87      public static boolean isValidCreateTemporalFiles(FacesContext facesContext)
88      {
89          if (WebConfigParamUtils.getBooleanInitParameter(facesContext.getExternalContext(),
90              INIT_PARAM_TEMPORAL_RESOURCEHANDLER_CACHE_ENABLED,
91              INIT_PARAM_TEMPORAL_RESOURCEHANDLER_CACHE_ENABLED_DEFAULT))
92          {
93              // Try create a temporal folder to check if is valid to do so, otherwise, disable it.
94              try
95              {
96                  Map<String, Object> applicationMap = facesContext.getExternalContext().getApplicationMap();
97                  File tempdir = (File) applicationMap.get("javax.servlet.context.tempdir");
98                  File imagesDir = new File(tempdir, TEMP_FOLDER_BASE_DIR);
99                  if (!imagesDir.exists())
100                 {
101                     imagesDir.mkdirs();
102                 }
103                 return true;
104             }
105             catch (Exception e)
106             {
107                 return false;
108             }
109         }
110         else
111         {
112             return false;
113         }
114     }
115     
116     protected void initialize()
117     {
118         //Get startup FacesContext
119         FacesContext facesContext = FacesContext.getCurrentInstance();
120     
121         //1. Create temporal directory for temporal resources
122         Map<String, Object> applicationMap = facesContext.getExternalContext().getApplicationMap();
123         File tempdir = (File) applicationMap.get("javax.servlet.context.tempdir");
124         File imagesDir = new File(tempdir, TEMP_FOLDER_BASE_DIR);
125         if (!imagesDir.exists())
126         {
127             imagesDir.mkdirs();
128         }
129         else
130         {
131             //Clear the cache
132             deleteDir(imagesDir);
133             imagesDir.mkdirs();
134         }
135         _tempDir = imagesDir;
136 
137         //2. Create map for register temporal resources
138         Map<String, FileProducer> temporalFilesLockMap = new ConcurrentHashMap<String, FileProducer>();
139         facesContext.getExternalContext().getApplicationMap().put(TEMP_FILES_LOCK_MAP, temporalFilesLockMap);
140     }
141 
142     private static boolean deleteDir(File dir)
143     {
144         if (dir.isDirectory())
145         {
146             String[] children = dir.list();
147             for (int i = 0; i < children.length; i++)
148             {
149                 boolean success = deleteDir(new File(dir, children[i]));
150                 if (!success)
151                 {
152                     return false;
153                 }
154             }
155         }
156         return dir.delete();
157     }
158     
159     @Override
160     public URL getResourceURL(ResourceMeta resourceMeta)
161     {
162         FacesContext facesContext = FacesContext.getCurrentInstance();
163 
164         if (resourceExists(resourceMeta))
165         {
166             File file = createOrGetTempFile(facesContext, resourceMeta);
167             
168             try
169             {
170                 return file.toURL();
171             }
172             catch (MalformedURLException e)
173             {
174                 throw new FacesException(e);
175             }
176         }
177         else
178         {
179             return null;
180         }
181     }    
182     
183     public InputStream getResourceInputStream(ResourceMeta resourceMeta, Resource resource)
184     {
185         FacesContext facesContext = FacesContext.getCurrentInstance();
186 
187         if (resourceExists(resourceMeta))
188         {
189             File file = createOrGetTempFile(facesContext, resourceMeta);
190             
191             try
192             {
193                 return new BufferedInputStream(new FileInputStream(file));
194             }
195             catch (FileNotFoundException e)
196             {
197                 throw new FacesException(e);
198             }
199         }
200         else
201         {
202             return null;
203         }
204     }
205 
206     @Override
207     public InputStream getResourceInputStream(ResourceMeta resourceMeta)
208     {
209         return getResourceInputStream(resourceMeta, null);
210     }
211     
212     @Override
213     public boolean resourceExists(ResourceMeta resourceMeta)
214     {
215         return super.resourceExists(resourceMeta);
216     }
217 
218     @SuppressWarnings("unchecked")
219     private File createOrGetTempFile(FacesContext facesContext, ResourceMeta resourceMeta)
220     {
221         String identifier = resourceMeta.getResourceIdentifier();
222         File file = getTemporalFile(resourceMeta);
223         if (!file.exists())
224         {
225             Map<String, FileProducer> map = (Map<String, FileProducer>) 
226                 facesContext.getExternalContext().getApplicationMap().get(TEMP_FILES_LOCK_MAP);
227 
228             FileProducer creator = map.get(identifier);
229             
230             if (creator == null)
231             {
232                 synchronized(this)
233                 {
234                     creator = map.get(identifier);
235                     
236                     if (creator == null)
237                     {
238                         creator = new FileProducer();
239                         map.put(identifier, creator);
240                     }
241                 }
242             }
243             
244             if (!creator.isCreated())
245             {
246                 creator.createFile(facesContext, resourceMeta, file, this);
247             }
248         }
249         return file;
250     }    
251     
252     private File getTemporalFile(ResourceMeta resourceMeta)
253     {
254         return new File(_tempDir, resourceMeta.getResourceIdentifier() + TEMP_FILE_SUFFIX);
255     }
256 
257     /*
258     private boolean couldResourceContainValueExpressions(ResourceMeta resourceMeta)
259     {
260         return resourceMeta.couldResourceContainValueExpressions() || resourceMeta.getResourceName().endsWith(".css");
261     }*/
262     
263     protected void createTemporalFileVersion(FacesContext facesContext, ResourceMeta resourceMeta, File target)
264     {
265         target.mkdirs();  // ensure necessary directories exist
266         target.delete();  // remove any existing file
267 
268         InputStream inputStream = null;
269         FileOutputStream fileOutputStream;
270         try
271         {
272             /*
273             if (couldResourceContainValueExpressions(resourceMeta))
274             {
275                 inputStream = new ValueExpressionFilterInputStream(
276                         getWrapped().getResourceInputStream(resourceMeta),
277                         resourceMeta.getLibraryName(), 
278                         resourceMeta.getResourceName());
279             }
280             else
281             {*/
282                 inputStream = getWrapped().getResourceInputStream(resourceMeta);
283             /*}*/
284             fileOutputStream = new FileOutputStream(target);
285             byte[] buffer = new byte[this.getResourceBufferSize()];
286 
287             pipeBytes(inputStream, fileOutputStream, buffer);
288         }
289         catch (FileNotFoundException e)
290         {
291             throw new FacesException("Unexpected exception while create file:", e);
292         }
293         catch (IOException e)
294         {
295             throw new FacesException("Unexpected exception while create file:", e);
296         }
297         finally
298         {
299             if (inputStream != null)
300             {
301                 try
302                 {
303                     inputStream.close();
304                 }
305                 catch (IOException e)
306                 {
307                     // Ignore
308                 }
309             }
310         }
311     }
312     
313     /**
314      * Reads the specified input stream into the provided byte array storage and
315      * writes it to the output stream.
316      */
317     private static void pipeBytes(InputStream in, OutputStream out, byte[] buffer) throws IOException
318     {
319         int length;
320 
321         while ((length = (in.read(buffer))) >= 0)
322         {
323             out.write(buffer, 0, length);
324         }
325     }
326     
327     public static class FileProducer 
328     {
329         
330         public volatile boolean created = false;
331         
332         public FileProducer()
333         {
334             super();
335         }
336 
337         public boolean isCreated()
338         {
339             return created;
340         }
341 
342         public synchronized void createFile(FacesContext facesContext, 
343             ResourceMeta resourceMeta, File file, TempDirFileCacheResourceLoader loader)
344         {
345             if (!created)
346             {
347                 loader.createTemporalFileVersion(facesContext, resourceMeta, file);
348                 created = true;
349             }
350         }
351     }
352     
353     protected int getResourceBufferSize()
354     {
355         if (_resourceBufferSize == -1)
356         {
357             _resourceBufferSize = WebConfigParamUtils.getIntegerInitParameter(
358                 FacesContext.getCurrentInstance().getExternalContext(),
359                 ResourceHandlerImpl.INIT_PARAM_RESOURCE_BUFFER_SIZE,
360                 ResourceHandlerImpl.INIT_PARAM_RESOURCE_BUFFER_SIZE_DEFAULT);
361         }
362         return _resourceBufferSize;
363     }
364     
365     public ResourceLoader getWrapped()
366     {
367         return delegate;
368     }
369 }