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