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.shared.resource.ContractResourceLoader;
38  import org.apache.myfaces.shared.resource.ContractResourceLoaderWrapper;
39  import org.apache.myfaces.shared.resource.ResourceMeta;
40  import org.apache.myfaces.shared.util.WebConfigParamUtils;
41  
42  /**
43   * ResourceLoader that uses a temporal folder to cache resources, avoiding the problem
44   * described on  MYFACES-3586 (Performance improvement in Resource loading - 
45   * HIGH CPU inflating bytes in ResourceHandlerImpl.handleResourceRequest).
46   *
47   * @author Leonardo Uribe
48   */
49  public class TempDirFileCacheContractResourceLoader extends ContractResourceLoaderWrapper
50  {
51      
52      public final static String TEMP_FILES_LOCK_MAP = "oam.rh.con.TEMP_FILES_LOCK_MAP";
53      
54      /**
55       * Subdir of the ServletContext tmp dir to store temporal resources.
56       */
57      private static final String TEMP_FOLDER_BASE_DIR = "oam-rh-cache/";
58  
59      /**
60       * Suffix for temporal files.
61       */
62      private static final String TEMP_FILE_SUFFIX = ".tmp";
63      
64      private final ContractResourceLoader delegate;
65      
66      private volatile File _tempDir;
67      
68      private int _resourceBufferSize = -1;
69      
70      public TempDirFileCacheContractResourceLoader(ContractResourceLoader delegate)
71      {
72          this.delegate = delegate;
73          initialize();
74      }
75      
76      protected void initialize()
77      {
78          //Get startup FacesContext
79          FacesContext facesContext = FacesContext.getCurrentInstance();
80      
81          //1. Create temporal directory for temporal resources
82          Map<String, Object> applicationMap = facesContext.getExternalContext().getApplicationMap();
83          File tempdir = (File) applicationMap.get("javax.servlet.context.tempdir");
84          File imagesDir = new File(tempdir, TEMP_FOLDER_BASE_DIR);
85          if (!imagesDir.exists())
86          {
87              imagesDir.mkdirs();
88          }
89          else
90          {
91              //Clear the cache
92              deleteDir(imagesDir);
93              imagesDir.mkdirs();
94          }
95          _tempDir = imagesDir;
96  
97          //2. Create map for register temporal resources
98          Map<String, FileProducer> temporalFilesLockMap = new ConcurrentHashMap<String, FileProducer>();
99          facesContext.getExternalContext().getApplicationMap().put(TEMP_FILES_LOCK_MAP, temporalFilesLockMap);
100     }
101 
102     private static boolean deleteDir(File dir)
103     {
104         if (dir.isDirectory())
105         {
106             String[] children = dir.list();
107             for (int i = 0; i < children.length; i++)
108             {
109                 boolean success = deleteDir(new File(dir, children[i]));
110                 if (!success)
111                 {
112                     return false;
113                 }
114             }
115         }
116         return dir.delete();
117     }
118     
119     @Override
120     public URL getResourceURL(ResourceMeta resourceMeta)
121     {
122         FacesContext facesContext = FacesContext.getCurrentInstance();
123 
124         if (resourceExists(resourceMeta))
125         {
126             File file = createOrGetTempFile(facesContext, resourceMeta);
127             
128             try
129             {
130                 return file.toURL();
131             }
132             catch (MalformedURLException e)
133             {
134                 throw new FacesException(e);
135             }
136         }
137         else
138         {
139             return null;
140         }
141     }    
142     
143     public InputStream getResourceInputStream(ResourceMeta resourceMeta, Resource resource)
144     {
145         FacesContext facesContext = FacesContext.getCurrentInstance();
146 
147         if (resourceExists(resourceMeta))
148         {
149             File file = createOrGetTempFile(facesContext, resourceMeta);
150             
151             try
152             {
153                 return new BufferedInputStream(new FileInputStream(file));
154             }
155             catch (FileNotFoundException e)
156             {
157                 throw new FacesException(e);
158             }
159         }
160         else
161         {
162             return null;
163         }
164     }
165 
166     @Override
167     public InputStream getResourceInputStream(ResourceMeta resourceMeta)
168     {
169         return getResourceInputStream(resourceMeta, null);
170     }
171     
172     @Override
173     public boolean resourceExists(ResourceMeta resourceMeta)
174     {
175         return super.resourceExists(resourceMeta);
176     }
177 
178     @SuppressWarnings("unchecked")
179     private File createOrGetTempFile(FacesContext facesContext, ResourceMeta resourceMeta)
180     {
181         String identifier = resourceMeta.getResourceIdentifier()+"_"+resourceMeta.getContractName();
182         File file = getTemporalFile(resourceMeta);
183         if (!file.exists())
184         {
185             Map<String, FileProducer> map = (Map<String, FileProducer>) 
186                 facesContext.getExternalContext().getApplicationMap().get(TEMP_FILES_LOCK_MAP);
187 
188             FileProducer creator = map.get(identifier);
189             
190             if (creator == null)
191             {
192                 synchronized(this)
193                 {
194                     creator = map.get(identifier);
195                     
196                     if (creator == null)
197                     {
198                         creator = new FileProducer();
199                         map.put(identifier, creator);
200                     }
201                 }
202             }
203             
204             if (!creator.isCreated())
205             {
206                 creator.createFile(facesContext, resourceMeta, file, this);
207             }
208         }
209         return file;
210     }    
211     
212     private File getTemporalFile(ResourceMeta resourceMeta)
213     {
214         return new File(_tempDir, 
215             resourceMeta.getResourceIdentifier()+"_"+ resourceMeta.getContractName() + TEMP_FILE_SUFFIX);
216     }
217 
218     protected void createTemporalFileVersion(FacesContext facesContext, ResourceMeta resourceMeta, File target)
219     {
220         target.mkdirs();  // ensure necessary directories exist
221         target.delete();  // remove any existing file
222 
223         InputStream inputStream = null;
224         FileOutputStream fileOutputStream;
225         try
226         {
227             inputStream = getWrapped().getResourceInputStream(resourceMeta);
228             fileOutputStream = new FileOutputStream(target);
229             byte[] buffer = new byte[this.getResourceBufferSize()];
230 
231             pipeBytes(inputStream, fileOutputStream, buffer);
232         }
233         catch (FileNotFoundException e)
234         {
235             throw new FacesException("Unexpected exception while create file:", e);
236         }
237         catch (IOException e)
238         {
239             throw new FacesException("Unexpected exception while create file:", e);
240         }
241         finally
242         {
243             if (inputStream != null)
244             {
245                 try
246                 {
247                     inputStream.close();
248                 }
249                 catch (IOException e)
250                 {
251                     // Ignore
252                 }
253             }
254         }
255     }
256     
257     /**
258      * Reads the specified input stream into the provided byte array storage and
259      * writes it to the output stream.
260      */
261     private static void pipeBytes(InputStream in, OutputStream out, byte[] buffer) throws IOException
262     {
263         int length;
264 
265         while ((length = (in.read(buffer))) >= 0)
266         {
267             out.write(buffer, 0, length);
268         }
269     }
270     
271     public static class FileProducer 
272     {
273         
274         public volatile boolean created = false;
275         
276         public FileProducer()
277         {
278             super();
279         }
280 
281         public boolean isCreated()
282         {
283             return created;
284         }
285 
286         public synchronized void createFile(FacesContext facesContext, 
287             ResourceMeta resourceMeta, File file, TempDirFileCacheContractResourceLoader loader)
288         {
289             if (!created)
290             {
291                 loader.createTemporalFileVersion(facesContext, resourceMeta, file);
292                 created = true;
293             }
294         }
295     }
296     
297     protected int getResourceBufferSize()
298     {
299         if (_resourceBufferSize == -1)
300         {
301             _resourceBufferSize = WebConfigParamUtils.getIntegerInitParameter(
302                 FacesContext.getCurrentInstance().getExternalContext(),
303                 ResourceHandlerImpl.INIT_PARAM_RESOURCE_BUFFER_SIZE,
304                 ResourceHandlerImpl.INIT_PARAM_RESOURCE_BUFFER_SIZE_DEFAULT);
305         }
306         return _resourceBufferSize;
307     }
308     
309     public ContractResourceLoader getWrapped()
310     {
311         return delegate;
312     }
313 }