View Javadoc

1   package org.apache.velocity.runtime.resource.loader;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.BufferedInputStream;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.FileNotFoundException;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.util.ArrayList;
29  import java.util.Collections;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Map;
33  
34  import org.apache.commons.collections.ExtendedProperties;
35  import org.apache.velocity.exception.ResourceNotFoundException;
36  import org.apache.velocity.exception.VelocityException;
37  import org.apache.velocity.io.UnicodeInputStream;
38  import org.apache.velocity.runtime.resource.Resource;
39  import org.apache.velocity.util.StringUtils;
40  
41  /**
42   * A loader for templates stored on the file system.  Treats the template
43   * as relative to the configured root path.  If the root path is empty
44   * treats the template name as an absolute path.
45   *
46   * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
47   * @author <a href="mailto:mailmur@yahoo.com">Aki Nieminen</a>
48   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
49   * @version $Id: FileResourceLoader.java 743616 2009-02-12 04:38:53Z nbubna $
50   */
51  public class FileResourceLoader extends ResourceLoader
52  {
53      /**
54       * The paths to search for templates.
55       */
56      private List paths = new ArrayList();
57  
58      /**
59       * Used to map the path that a template was found on
60       * so that we can properly check the modification
61       * times of the files. This is synchronizedMap
62       * instance.
63       */
64      private Map templatePaths = Collections.synchronizedMap(new HashMap());
65  
66      /** Shall we inspect unicode files to see what encoding they contain?. */
67      private boolean unicode = false;
68  
69      /**
70       * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#init(org.apache.commons.collections.ExtendedProperties)
71       */
72      public void init( ExtendedProperties configuration)
73      {
74          if (log.isTraceEnabled())
75          {
76              log.trace("FileResourceLoader : initialization starting.");
77          }
78  
79          paths.addAll( configuration.getVector("path") );
80  
81          // unicode files may have a BOM marker at the start, but Java
82          // has problems recognizing the UTF-8 bom. Enabling unicode will
83          // recognize all unicode boms.
84          unicode = configuration.getBoolean("unicode", false);
85  
86          if (log.isDebugEnabled())
87          {
88              log.debug("Do unicode file recognition:  " + unicode);
89          }
90  
91          if (log.isDebugEnabled())
92          {
93              // trim spaces from all paths
94              StringUtils.trimStrings(paths);
95  
96              // this section lets tell people what paths we will be using
97              int sz = paths.size();
98              for( int i=0; i < sz; i++)
99              {
100                 log.debug("FileResourceLoader : adding path '" + (String) paths.get(i) + "'");
101             }
102             log.trace("FileResourceLoader : initialization complete.");
103         }
104     }
105 
106     /**
107      * Get an InputStream so that the Runtime can build a
108      * template with it.
109      *
110      * @param templateName name of template to get
111      * @return InputStream containing the template
112      * @throws ResourceNotFoundException if template not found
113      *         in the file template path.
114      */
115     public InputStream getResourceStream(String templateName)
116         throws ResourceNotFoundException
117     {
118         /*
119          * Make sure we have a valid templateName.
120          */
121         if (org.apache.commons.lang.StringUtils.isEmpty(templateName))
122         {
123             /*
124              * If we don't get a properly formed templateName then
125              * there's not much we can do. So we'll forget about
126              * trying to search any more paths for the template.
127              */
128             throw new ResourceNotFoundException(
129                 "Need to specify a file name or file path!");
130         }
131 
132         String template = StringUtils.normalizePath(templateName);
133         if ( template == null || template.length() == 0 )
134         {
135             String msg = "File resource error : argument " + template +
136                 " contains .. and may be trying to access " +
137                 "content outside of template root.  Rejected.";
138 
139             log.error("FileResourceLoader : " + msg);
140 
141             throw new ResourceNotFoundException ( msg );
142         }
143 
144         int size = paths.size();
145         for (int i = 0; i < size; i++)
146         {
147             String path = (String) paths.get(i);
148             InputStream inputStream = null;
149 
150             try
151             {
152                 inputStream = findTemplate(path, template);
153             }
154             catch (IOException ioe)
155             {
156                 String msg = "Exception while loading Template " + template;
157                 log.error(msg, ioe);
158                 throw new VelocityException(msg, ioe);
159             }
160 
161             if (inputStream != null)
162             {
163                 /*
164                  * Store the path that this template came
165                  * from so that we can check its modification
166                  * time.
167                  */
168                 templatePaths.put(templateName, path);
169                 return inputStream;
170             }
171         }
172 
173         /*
174          * We have now searched all the paths for
175          * templates and we didn't find anything so
176          * throw an exception.
177          */
178          throw new ResourceNotFoundException("FileResourceLoader : cannot find " + template);
179     }
180 
181     /**
182      * Overrides superclass for better performance.
183      * @since 1.6
184      */
185     public boolean resourceExists(String name)
186     {
187         if (name == null)
188         {
189             return false;
190         }
191         name = StringUtils.normalizePath(name);
192         if (name == null || name.length() == 0)
193         {
194             return false;
195         }
196 
197         int size = paths.size();
198         for (int i = 0; i < size; i++)
199         {
200             String path = (String)paths.get(i);
201             try
202             {
203                 File file = getFile(path, name);
204                 if (file.canRead())
205                 {
206                     return true;
207                 }
208             }
209             catch (Exception ioe)
210             {
211                 String msg = "Exception while checking for template " + name;
212                 log.debug(msg, ioe);
213             }
214         }
215         return false;
216     }
217 
218     /**
219      * Try to find a template given a normalized path.
220      *
221      * @param path a normalized path
222      * @param template name of template to find
223      * @return InputStream input stream that will be parsed
224      *
225      */
226     private InputStream findTemplate(final String path, final String template)
227         throws IOException
228     {
229         try
230         {
231             File file = getFile(path,template);
232 
233             if (file.canRead())
234             {
235                 FileInputStream fis = null;
236                 try
237                 {
238                     fis = new FileInputStream(file.getAbsolutePath());
239 
240                     if (unicode)
241                     {
242                         UnicodeInputStream uis = null;
243 
244                         try
245                         {
246                             uis = new UnicodeInputStream(fis, true);
247 
248                             if (log.isDebugEnabled())
249                             {
250                                 log.debug("File Encoding for " + file + " is: " + uis.getEncodingFromStream());
251                             }
252 
253                             return new BufferedInputStream(uis);
254                         }
255                         catch(IOException e)
256                         {
257                             closeQuiet(uis);
258                             throw e;
259                         }
260                     }
261                     else
262                     {
263                         return new BufferedInputStream(fis);
264                     }
265                 }
266                 catch (IOException e)
267                 {
268                     closeQuiet(fis);
269                     throw e;
270                 }
271             }
272             else
273             {
274                 return null;
275             }
276         }
277         catch(FileNotFoundException fnfe)
278         {
279             /*
280              *  log and convert to a general Velocity ResourceNotFoundException
281              */
282             return null;
283         }
284     }
285 
286     private void closeQuiet(final InputStream is)
287     {
288         if (is != null)
289         {
290             try
291             {
292                 is.close();
293             }
294             catch(IOException ioe)
295             {
296                 // Ignore
297             }
298         }
299     }
300 
301     /**
302      * How to keep track of all the modified times
303      * across the paths.  Note that a file might have
304      * appeared in a directory which is earlier in the
305      * path; so we should search the path and see if
306      * the file we find that way is the same as the one
307      * that we have cached.
308      * @param resource
309      * @return True if the source has been modified.
310      */
311     public boolean isSourceModified(Resource resource)
312     {
313         /*
314          * we assume that the file needs to be reloaded;
315          * if we find the original file and it's unchanged,
316          * then we'll flip this.
317          */
318         boolean modified = true;
319 
320         String fileName = resource.getName();
321         String path = (String) templatePaths.get(fileName);
322         File currentFile = null;
323 
324         for (int i = 0; currentFile == null && i < paths.size(); i++)
325         {
326             String testPath = (String) paths.get(i);
327             File testFile = getFile(testPath, fileName);
328             if (testFile.canRead())
329             {
330                 currentFile = testFile;
331             }
332         }
333         File file = getFile(path, fileName);
334         if (currentFile == null || !file.exists())
335         {
336             /*
337              * noop: if the file is missing now (either the cached
338              * file is gone, or the file can no longer be found)
339              * then we leave modified alone (it's set to true); a
340              * reload attempt will be done, which will either use
341              * a new template or fail with an appropriate message
342              * about how the file couldn't be found.
343              */
344         }
345         else if (currentFile.equals(file) && file.canRead())
346         {
347             /*
348              * if only if currentFile is the same as file and
349              * file.lastModified() is the same as
350              * resource.getLastModified(), then we should use the
351              * cached version.
352              */
353             modified = (file.lastModified() != resource.getLastModified());
354         }
355 
356         /*
357          * rsvc.debug("isSourceModified for " + fileName + ": " + modified);
358          */
359         return modified;
360     }
361 
362     /**
363      * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource)
364      */
365     public long getLastModified(Resource resource)
366     {
367         String path = (String) templatePaths.get(resource.getName());
368         File file = getFile(path, resource.getName());
369 
370         if (file.canRead())
371         {
372             return file.lastModified();
373         }
374         else
375         {
376             return 0;
377         }
378     }
379 
380 
381     /**
382      * Create a File based on either a relative path if given, or absolute path otherwise
383      */
384     private File getFile(String path, String template)
385     {
386 
387         File file = null;
388 
389         if("".equals(path))
390         {
391             file = new File( template );
392         }
393         else
394         {
395             /*
396              *  if a / leads off, then just nip that :)
397              */
398             if (template.startsWith("/"))
399             {
400                 template = template.substring(1);
401             }
402 
403             file = new File ( path, template );
404         }
405 
406         return file;
407     }
408 }