001: package org.apache.velocity.runtime.resource.loader;
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.BufferedInputStream;
023: import java.io.File;
024: import java.io.FileInputStream;
025: import java.io.FileNotFoundException;
026: import java.io.IOException;
027: import java.io.InputStream;
028: import java.util.ArrayList;
029: import java.util.Collections;
030: import java.util.HashMap;
031: import java.util.List;
032: import java.util.Map;
033:
034: import org.apache.commons.collections.ExtendedProperties;
035: import org.apache.velocity.exception.ResourceNotFoundException;
036: import org.apache.velocity.io.UnicodeInputStream;
037: import org.apache.velocity.runtime.resource.Resource;
038: import org.apache.velocity.util.StringUtils;
039:
040: /**
041: * A loader for templates stored on the file system. Treats the template
042: * as relative to the configured root path. If the root path is empty
043: * treats the template name as an absolute path.
044: *
045: * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
046: * @author <a href="mailto:mailmur@yahoo.com">Aki Nieminen</a>
047: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
048: * @version $Id: FileResourceLoader.java 499441 2007-01-24 15:25:58Z henning $
049: */
050: public class FileResourceLoader extends ResourceLoader {
051: /**
052: * The paths to search for templates.
053: */
054: private List paths = new ArrayList();
055:
056: /**
057: * Used to map the path that a template was found on
058: * so that we can properly check the modification
059: * times of the files. This is synchronizedMap
060: * instance.
061: */
062: private Map templatePaths = Collections
063: .synchronizedMap(new HashMap());
064:
065: /** Shall we inspect unicode files to see what encoding they contain?. */
066: private boolean unicode = false;
067:
068: /**
069: * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#init(org.apache.commons.collections.ExtendedProperties)
070: */
071: public void init(ExtendedProperties configuration) {
072: if (log.isTraceEnabled()) {
073: log.trace("FileResourceLoader : initialization starting.");
074: }
075:
076: paths.addAll(configuration.getVector("path"));
077:
078: // unicode files may have a BOM marker at the start, but Java
079: // has problems recognizing the UTF-8 bom. Enabling unicode will
080: // recognize all unicode boms.
081: unicode = configuration.getBoolean("unicode", false);
082:
083: if (log.isDebugEnabled()) {
084: log.debug("Do unicode file recognition: " + unicode);
085: }
086:
087: // trim spaces from all paths
088: StringUtils.trimStrings(paths);
089: if (log.isInfoEnabled()) {
090: // this section lets tell people what paths we will be using
091: int sz = paths.size();
092: for (int i = 0; i < sz; i++) {
093: log.info("FileResourceLoader : adding path '"
094: + (String) paths.get(i) + "'");
095: }
096: log.trace("FileResourceLoader : initialization complete.");
097: }
098: }
099:
100: /**
101: * Get an InputStream so that the Runtime can build a
102: * template with it.
103: *
104: * @param templateName name of template to get
105: * @return InputStream containing the template
106: * @throws ResourceNotFoundException if template not found
107: * in the file template path.
108: */
109: public InputStream getResourceStream(String templateName)
110: throws ResourceNotFoundException {
111: /*
112: * Make sure we have a valid templateName.
113: */
114: if (org.apache.commons.lang.StringUtils.isEmpty(templateName)) {
115: /*
116: * If we don't get a properly formed templateName then
117: * there's not much we can do. So we'll forget about
118: * trying to search any more paths for the template.
119: */
120: throw new ResourceNotFoundException(
121: "Need to specify a file name or file path!");
122: }
123:
124: String template = StringUtils.normalizePath(templateName);
125: if (template == null || template.length() == 0) {
126: String msg = "File resource error : argument " + template
127: + " contains .. and may be trying to access "
128: + "content outside of template root. Rejected.";
129:
130: log.error("FileResourceLoader : " + msg);
131:
132: throw new ResourceNotFoundException(msg);
133: }
134:
135: int size = paths.size();
136: for (int i = 0; i < size; i++) {
137: String path = (String) paths.get(i);
138: InputStream inputStream = null;
139:
140: try {
141: inputStream = findTemplate(path, template);
142: } catch (IOException ioe) {
143: log.error("While loading Template " + template + ": ",
144: ioe);
145: }
146:
147: if (inputStream != null) {
148: /*
149: * Store the path that this template came
150: * from so that we can check its modification
151: * time.
152: */
153: templatePaths.put(templateName, path);
154: return inputStream;
155: }
156: }
157:
158: /*
159: * We have now searched all the paths for
160: * templates and we didn't find anything so
161: * throw an exception.
162: */
163: throw new ResourceNotFoundException(
164: "FileResourceLoader : cannot find " + template);
165: }
166:
167: /**
168: * Try to find a template given a normalized path.
169: *
170: * @param path a normalized path
171: * @param template name of template to find
172: * @return InputStream input stream that will be parsed
173: *
174: */
175: private InputStream findTemplate(final String path,
176: final String template) throws IOException {
177: try {
178: File file = getFile(path, template);
179:
180: if (file.canRead()) {
181: FileInputStream fis = null;
182: try {
183: fis = new FileInputStream(file.getAbsolutePath());
184:
185: if (unicode) {
186: UnicodeInputStream uis = null;
187:
188: try {
189: uis = new UnicodeInputStream(fis, true);
190:
191: if (log.isDebugEnabled()) {
192: log.debug("File Encoding for " + file
193: + " is: "
194: + uis.getEncodingFromStream());
195: }
196:
197: return new BufferedInputStream(uis);
198: } catch (IOException e) {
199: closeQuiet(uis);
200: throw e;
201: }
202: } else {
203: return new BufferedInputStream(fis);
204: }
205: } catch (IOException e) {
206: closeQuiet(fis);
207: throw e;
208: }
209: } else {
210: return null;
211: }
212: } catch (FileNotFoundException fnfe) {
213: /*
214: * log and convert to a general Velocity ResourceNotFoundException
215: */
216: return null;
217: }
218: }
219:
220: private void closeQuiet(final InputStream is) {
221: if (is != null) {
222: try {
223: is.close();
224: } catch (IOException ioe) {
225: // Ignore
226: }
227: }
228: }
229:
230: /**
231: * How to keep track of all the modified times
232: * across the paths. Note that a file might have
233: * appeared in a directory which is earlier in the
234: * path; so we should search the path and see if
235: * the file we find that way is the same as the one
236: * that we have cached.
237: * @param resource
238: * @return True if the source has been modified.
239: */
240: public boolean isSourceModified(Resource resource) {
241: /*
242: * we assume that the file needs to be reloaded;
243: * if we find the original file and it's unchanged,
244: * then we'll flip this.
245: */
246: boolean modified = true;
247:
248: String fileName = resource.getName();
249: String path = (String) templatePaths.get(fileName);
250: File currentFile = null;
251:
252: for (int i = 0; currentFile == null && i < paths.size(); i++) {
253: String testPath = (String) paths.get(i);
254: File testFile = getFile(testPath, fileName);
255: if (testFile.canRead()) {
256: currentFile = testFile;
257: }
258: }
259: File file = getFile(path, fileName);
260: if (currentFile == null || !file.exists()) {
261: /*
262: * noop: if the file is missing now (either the cached
263: * file is gone, or the file can no longer be found)
264: * then we leave modified alone (it's set to true); a
265: * reload attempt will be done, which will either use
266: * a new template or fail with an appropriate message
267: * about how the file couldn't be found.
268: */
269: } else if (currentFile.equals(file) && file.canRead()) {
270: /*
271: * if only if currentFile is the same as file and
272: * file.lastModified() is the same as
273: * resource.getLastModified(), then we should use the
274: * cached version.
275: */
276: modified = (file.lastModified() != resource
277: .getLastModified());
278: }
279:
280: /*
281: * rsvc.debug("isSourceModified for " + fileName + ": " + modified);
282: */
283: return modified;
284: }
285:
286: /**
287: * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource)
288: */
289: public long getLastModified(Resource resource) {
290: String path = (String) templatePaths.get(resource.getName());
291: File file = getFile(path, resource.getName());
292:
293: if (file.canRead()) {
294: return file.lastModified();
295: } else {
296: return 0;
297: }
298: }
299:
300: /**
301: * Create a File based on either a relative path if given, or absolute path otherwise
302: */
303: private File getFile(String path, String template) {
304:
305: File file = null;
306:
307: if ("".equals(path)) {
308: file = new File(template);
309: } else {
310: /*
311: * if a / leads off, then just nip that :)
312: */
313: if (template.startsWith("/")) {
314: template = template.substring(1);
315: }
316:
317: file = new File(path, template);
318: }
319:
320: return file;
321: }
322: }
|