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