001: package org.apache.velocity.tools.view.servlet;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.io.File;
023: import java.io.InputStream;
024: import java.util.HashMap;
025:
026: import javax.servlet.ServletContext;
027:
028: import org.apache.commons.collections.ExtendedProperties;
029: import org.apache.velocity.exception.ResourceNotFoundException;
030: import org.apache.velocity.runtime.resource.Resource;
031: import org.apache.velocity.runtime.resource.loader.ResourceLoader;
032:
033: /**
034: * Resource loader that uses the ServletContext of a webapp to
035: * load Velocity templates. (it's much easier to use with servlets than
036: * the standard FileResourceLoader, in particular the use of war files
037: * is transparent).
038: *
039: * The default search path is '/' (relative to the webapp root), but
040: * you can change this behaviour by specifying one or more paths
041: * by mean of as many webapp.resource.loader.path properties as needed
042: * in the velocity.properties file.
043: *
044: * All paths must be relative to the root of the webapp.
045: *
046: * To enable caching and cache refreshing the webapp.resource.loader.cache and
047: * webapp.resource.loader.modificationCheckInterval properties need to be
048: * set in the velocity.properties file ... auto-reloading of global macros
049: * requires the webapp.resource.loader.cache property to be set to 'false'.
050: *
051: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
052: * @author Nathan Bubna
053: * @author <a href="mailto:claude@savoirweb.com">Claude Brisson</a>
054: * @version $Id: WebappLoader.java 479724 2006-11-27 18:49:37Z nbubna $ */
055:
056: public class WebappLoader extends ResourceLoader {
057:
058: /** The root paths for templates (relative to webapp's root). */
059: protected String[] paths = null;
060: protected HashMap templatePaths = null;
061:
062: protected ServletContext servletContext = null;
063:
064: /**
065: * This is abstract in the base class, so we need it.
066: * <br>
067: * NOTE: this expects that the ServletContext has already
068: * been placed in the runtime's application attributes
069: * under its full class name (i.e. "javax.servlet.ServletContext").
070: *
071: * @param configuration the {@link ExtendedProperties} associated with
072: * this resource loader.
073: */
074: public void init(ExtendedProperties configuration) {
075: rsvc.debug("WebappLoader : initialization starting.");
076:
077: /* get configured paths */
078: paths = configuration.getStringArray("path");
079: if (paths == null || paths.length == 0) {
080: paths = new String[1];
081: paths[0] = "/";
082: } else {
083: /* make sure the paths end with a '/' */
084: for (int i = 0; i < paths.length; i++) {
085: if (!paths[i].endsWith("/")) {
086: paths[i] += '/';
087: }
088: rsvc.info("WebappLoader : added template path - '"
089: + paths[i] + "'");
090: }
091: }
092:
093: /* get the ServletContext */
094: Object obj = rsvc.getApplicationAttribute(ServletContext.class
095: .getName());
096: if (obj instanceof ServletContext) {
097: servletContext = (ServletContext) obj;
098: } else {
099: rsvc
100: .error("WebappLoader : unable to retrieve ServletContext");
101: }
102:
103: /* init the template paths map */
104: templatePaths = new HashMap();
105:
106: rsvc.debug("WebappLoader : initialization complete.");
107: }
108:
109: /**
110: * Get an InputStream so that the Runtime can build a
111: * template with it.
112: *
113: * @param name name of template to get
114: * @return InputStream containing the template
115: * @throws ResourceNotFoundException if template not found
116: * in classpath.
117: */
118: public synchronized InputStream getResourceStream(String name)
119: throws ResourceNotFoundException {
120: InputStream result = null;
121:
122: if (name == null || name.length() == 0) {
123: throw new ResourceNotFoundException(
124: "WebappLoader : No template name provided");
125: }
126:
127: /* since the paths always ends in '/',
128: * make sure the name never starts with one */
129: while (name.startsWith("/")) {
130: name = name.substring(1);
131: }
132:
133: Exception exception = null;
134: for (int i = 0; i < paths.length; i++) {
135: try {
136: result = servletContext.getResourceAsStream(paths[i]
137: + name);
138:
139: /* save the path and exit the loop if we found the template */
140: if (result != null) {
141: templatePaths.put(name, paths[i]);
142: break;
143: }
144: } catch (Exception e) {
145: /* only save the first one for later throwing */
146: if (exception == null) {
147: exception = e;
148: }
149: }
150: }
151:
152: /* if we never found the template */
153: if (result == null) {
154: String msg;
155: if (exception == null) {
156: msg = "WebappLoader : Resource '" + name
157: + "' not found.";
158: } else {
159: msg = exception.getMessage();
160: }
161: /* convert to a general Velocity ResourceNotFoundException */
162: throw new ResourceNotFoundException(msg);
163: }
164:
165: return result;
166: }
167:
168: private File getCachedFile(String rootPath, String fileName) {
169: // we do this when we cache a resource,
170: // so do it again to ensure a match
171: while (fileName.startsWith("/")) {
172: fileName = fileName.substring(1);
173: }
174:
175: String savedPath = (String) templatePaths.get(fileName);
176: return new File(rootPath + savedPath, fileName);
177: }
178:
179: /**
180: * Checks to see if a resource has been deleted, moved or modified.
181: *
182: * @param resource Resource The resource to check for modification
183: * @return boolean True if the resource has been modified
184: */
185: public boolean isSourceModified(Resource resource) {
186: String rootPath = servletContext.getRealPath("/");
187: if (rootPath == null) {
188: // rootPath is null if the servlet container cannot translate the
189: // virtual path to a real path for any reason (such as when the
190: // content is being made available from a .war archive)
191: return false;
192: }
193:
194: // first, try getting the previously found file
195: String fileName = resource.getName();
196: File cachedFile = getCachedFile(rootPath, fileName);
197: if (!cachedFile.exists()) {
198: /* then the source has been moved and/or deleted */
199: return true;
200: }
201:
202: /* check to see if the file can now be found elsewhere
203: * before it is found in the previously saved path */
204: File currentFile = null;
205: for (int i = 0; i < paths.length; i++) {
206: currentFile = new File(rootPath + paths[i], fileName);
207: if (currentFile.canRead()) {
208: /* stop at the first resource found
209: * (just like in getResourceStream()) */
210: break;
211: }
212: }
213:
214: /* if the current is the cached and it is readable */
215: if (cachedFile.equals(currentFile) && cachedFile.canRead()) {
216: /* then (and only then) do we compare the last modified values */
217: return (cachedFile.lastModified() != resource
218: .getLastModified());
219: } else {
220: /* we found a new file for the resource
221: * or the resource is no longer readable. */
222: return true;
223: }
224: }
225:
226: /**
227: * Checks to see when a resource was last modified
228: *
229: * @param resource Resource the resource to check
230: * @return long The time when the resource was last modified or 0 if the file can't be read
231: */
232: public long getLastModified(Resource resource) {
233: String rootPath = servletContext.getRealPath("/");
234: if (rootPath == null) {
235: // rootPath is null if the servlet container cannot translate the
236: // virtual path to a real path for any reason (such as when the
237: // content is being made available from a .war archive)
238: return 0;
239: }
240:
241: File cachedFile = getCachedFile(rootPath, resource.getName());
242: if (cachedFile.canRead()) {
243: return cachedFile.lastModified();
244: } else {
245: return 0;
246: }
247: }
248: }
|