View Javadoc

1   package org.apache.velocity.tools.view;
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.File;
23  import java.io.InputStream;
24  import java.util.HashMap;
25  import javax.servlet.ServletContext;
26  import org.apache.commons.collections.ExtendedProperties;
27  import org.apache.velocity.exception.ResourceNotFoundException;
28  import org.apache.velocity.runtime.resource.Resource;
29  import org.apache.velocity.runtime.resource.loader.ResourceLoader;
30  
31  /**
32   * Resource loader that uses the ServletContext of a webapp to
33   * load Velocity templates.  (it's much easier to use with servlets than
34   * the standard FileResourceLoader, in particular the use of war files
35   * is transparent).
36   *
37   * The default search path is '/' (relative to the webapp root), but
38   * you can change this behaviour by specifying one or more paths
39   * by mean of as many webapp.resource.loader.path properties as needed
40   * in the velocity.properties file.
41   *
42   * All paths must be relative to the root of the webapp.
43   *
44   * To enable caching and cache refreshing the webapp.resource.loader.cache and
45   * webapp.resource.loader.modificationCheckInterval properties need to be
46   * set in the velocity.properties file ... auto-reloading of global macros
47   * requires the webapp.resource.loader.cache property to be set to 'false'.
48   *
49   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
50   * @author Nathan Bubna
51   * @author <a href="mailto:claude@savoirweb.com">Claude Brisson</a>
52   * @version $Id: WebappResourceLoader.java 712011 2008-11-06 23:35:43Z nbubna $  */
53  
54  public class WebappResourceLoader extends ResourceLoader
55  {
56      /** The root paths for templates (relative to webapp's root). */
57      protected String[] paths = null;
58      protected HashMap templatePaths = null;
59      protected ServletContext servletContext = null;
60  
61  
62      /**
63       *  This is abstract in the base class, so we need it.
64       *  <br>
65       *  NOTE: this expects that the ServletContext has already
66       *        been placed in the runtime's application attributes
67       *        under its full class name (i.e. "javax.servlet.ServletContext").
68       *
69       * @param configuration the {@link ExtendedProperties} associated with
70       *        this resource loader.
71       */
72      public void init(ExtendedProperties configuration)
73      {
74          log.trace("WebappResourceLoader: initialization starting.");
75  
76          /* get configured paths */
77          paths = configuration.getStringArray("path");
78          if (paths == null || paths.length == 0)
79          {
80              paths = new String[1];
81              paths[0] = "/";
82          }
83          else
84          {
85              /* make sure the paths end with a '/' */
86              for (int i=0; i < paths.length; i++)
87              {
88                  if (!paths[i].endsWith("/"))
89                  {
90                      paths[i] += '/';
91                  }
92                  log.info("WebappResourceLoader: added template path - '" + paths[i] + "'");
93              }
94          }
95  
96          /* get the ServletContext */
97          Object obj = rsvc.getApplicationAttribute(ServletContext.class.getName());
98          if (obj instanceof ServletContext)
99          {
100             servletContext = (ServletContext)obj;
101         }
102         else
103         {
104             log.error("WebappResourceLoader: unable to retrieve ServletContext");
105         }
106 
107         /* init the template paths map */
108         templatePaths = new HashMap();
109 
110         log.trace("WebappResourceLoader: initialization complete.");
111     }
112 
113     /**
114      * Get an InputStream so that the Runtime can build a
115      * template with it.
116      *
117      * @param name name of template to get
118      * @return InputStream containing the template
119      * @throws ResourceNotFoundException if template not found
120      *         in  classpath.
121      */
122     public synchronized InputStream getResourceStream(String name)
123         throws ResourceNotFoundException
124     {
125         InputStream result = null;
126 
127         if (name == null || name.length() == 0)
128         {
129             throw new ResourceNotFoundException("WebappResourceLoader: No template name provided");
130         }
131 
132         /* since the paths always ends in '/',
133          * make sure the name never starts with one */
134         while (name.startsWith("/"))
135         {
136             name = name.substring(1);
137         }
138 
139         Exception exception = null;
140         for (int i = 0; i < paths.length; i++)
141         {
142             String path = paths[i] + name;
143             try
144             {
145                 result = servletContext.getResourceAsStream(path);
146 
147                 /* save the path and exit the loop if we found the template */
148                 if (result != null)
149                 {
150                     templatePaths.put(name, paths[i]);
151                     break;
152                 }
153             }
154             catch (NullPointerException npe)
155             {
156                 /* no servletContext was set, whine about it! */
157                 throw npe;
158             }
159             catch (Exception e)
160             {
161                 /* only save the first one for later throwing */
162                 if (exception == null)
163                 {
164                     if (log.isDebugEnabled())
165                     {
166                         log.debug("WebappResourceLoader: Could not load "+path, e);
167                     }
168                     exception = e;
169                 }
170             }
171         }
172 
173         /* if we never found the template */
174         if (result == null)
175         {
176             String msg = "WebappResourceLoader: Resource '" + name + "' not found.";
177 
178             /* convert to a general Velocity ResourceNotFoundException */
179             if (exception == null)
180             {
181                 throw new ResourceNotFoundException(msg);
182             }
183             else
184             {
185                 msg += "  Due to: " + exception;
186                 throw new ResourceNotFoundException(msg, exception);
187             }
188         }
189         return result;
190     }
191 
192     private File getCachedFile(String rootPath, String fileName)
193     {
194         // we do this when we cache a resource,
195         // so do it again to ensure a match
196         while (fileName.startsWith("/"))
197         {
198             fileName = fileName.substring(1);
199         }
200 
201         String savedPath = (String)templatePaths.get(fileName);
202         return new File(rootPath + savedPath, fileName);
203     }
204 
205 
206     /**
207      * Checks to see if a resource has been deleted, moved or modified.
208      *
209      * @param resource Resource  The resource to check for modification
210      * @return boolean  True if the resource has been modified
211      */
212     public boolean isSourceModified(Resource resource)
213     {
214         String rootPath = servletContext.getRealPath("/");
215         if (rootPath == null) {
216             // rootPath is null if the servlet container cannot translate the
217             // virtual path to a real path for any reason (such as when the
218             // content is being made available from a .war archive)
219             return false;
220         }
221 
222         // first, try getting the previously found file
223         String fileName = resource.getName();
224         File cachedFile = getCachedFile(rootPath, fileName);
225         if (!cachedFile.exists())
226         {
227             /* then the source has been moved and/or deleted */
228             return true;
229         }
230 
231         /* check to see if the file can now be found elsewhere
232          * before it is found in the previously saved path */
233         File currentFile = null;
234         for (int i = 0; i < paths.length; i++)
235         {
236             currentFile = new File(rootPath + paths[i], fileName);
237             if (currentFile.canRead())
238             {
239                 /* stop at the first resource found
240                  * (just like in getResourceStream()) */
241                 break;
242             }
243         }
244 
245         /* if the current is the cached and it is readable */
246         if (cachedFile.equals(currentFile) && cachedFile.canRead())
247         {
248             /* then (and only then) do we compare the last modified values */
249             return (cachedFile.lastModified() != resource.getLastModified());
250         }
251         else
252         {
253             /* we found a new file for the resource
254              * or the resource is no longer readable. */
255             return true;
256         }
257     }
258 
259     /**
260      * Checks to see when a resource was last modified
261      *
262      * @param resource Resource the resource to check
263      * @return long The time when the resource was last modified or 0 if the file can't be read
264      */
265     public long getLastModified(Resource resource)
266     {
267         String rootPath = servletContext.getRealPath("/");
268         if (rootPath == null) {
269             // rootPath is null if the servlet container cannot translate the
270             // virtual path to a real path for any reason (such as when the
271             // content is being made available from a .war archive)
272             return 0;
273         }
274 
275         File cachedFile = getCachedFile(rootPath, resource.getName());
276         if (cachedFile.canRead())
277         {
278             return cachedFile.lastModified();
279         }
280         else
281         {
282             return 0;
283         }
284     }
285 }