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