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.InputStream;
023:
024: import java.util.Hashtable;
025: import java.util.Vector;
026: import java.util.Map;
027: import java.util.HashMap;
028:
029: import org.apache.velocity.util.StringUtils;
030: import org.apache.velocity.runtime.resource.Resource;
031: import org.apache.velocity.exception.ResourceNotFoundException;
032: import org.apache.commons.collections.ExtendedProperties;
033:
034: /**
035: * <p>
036: * ResourceLoader to load templates from multiple Jar files.
037: * </p>
038: * <p>
039: * The configuration of the JarResourceLoader is straightforward -
040: * You simply add the JarResourceLoader to the configuration via
041: * </p>
042: * <p><pre>
043: * resource.loader = jar
044: * jar.resource.loader.class = org.apache.velocity.runtime.resource.loader.JarResourceLoader
045: * jar.resource.loader.path = list of JAR <URL>s
046: * </pre></p>
047: *
048: * <p> So for example, if you had a jar file on your local filesystem, you could simply do
049: * <pre>
050: * jar.resource.loader.path = jar:file:/opt/myfiles/jar1.jar
051: * </pre>
052: * </p>
053: * <p> Note that jar specification for the <code>.path</code> configuration property
054: * conforms to the same rules for the java.net.JarUrlConnection class.
055: * </p>
056: *
057: * <p> For a working example, see the unit test case,
058: * org.apache.velocity.test.MultiLoaderTestCase class
059: * </p>
060: *
061: * @author <a href="mailto:mailmur@yahoo.com">Aki Nieminen</a>
062: * @author <a href="mailto:daveb@miceda-data.com">Dave Bryson</a>
063: * @version $Id: JarResourceLoader.java 471259 2006-11-04 20:26:57Z henning $
064: */
065: public class JarResourceLoader extends ResourceLoader {
066: /**
067: * Maps entries to the parent JAR File
068: * Key = the entry *excluding* plain directories
069: * Value = the JAR URL
070: */
071: private Map entryDirectory = new HashMap(559);
072:
073: /**
074: * Maps JAR URLs to the actual JAR
075: * Key = the JAR URL
076: * Value = the JAR
077: */
078: private Map jarfiles = new HashMap(89);
079:
080: /**
081: * Called by Velocity to initialize the loader
082: * @param configuration
083: */
084: public void init(ExtendedProperties configuration) {
085: log.trace("JarResourceLoader : initialization starting.");
086:
087: // rest of Velocity engine still use legacy Vector
088: // and Hashtable classes. Classes are implicitly
089: // synchronized even if we don't need it.
090: Vector paths = configuration.getVector("path");
091: StringUtils.trimStrings(paths);
092:
093: /*
094: * support the old version but deprecate with a log message
095: */
096:
097: if (paths == null || paths.size() == 0) {
098: paths = configuration.getVector("resource.path");
099: StringUtils.trimStrings(paths);
100:
101: if (paths != null && paths.size() > 0) {
102: log
103: .info("JarResourceLoader : you are using a deprecated configuration"
104: + " property for the JarResourceLoader -> '<name>.resource.loader.resource.path'."
105: + " Please change to the conventional '<name>.resource.loader.path'.");
106: }
107: }
108:
109: if (paths != null) {
110: log.debug("JarResourceLoader # of paths : " + paths.size());
111:
112: for (int i = 0; i < paths.size(); i++) {
113: loadJar((String) paths.get(i));
114: }
115: }
116:
117: log.trace("JarResourceLoader : initialization complete.");
118: }
119:
120: private void loadJar(String path) {
121: if (log.isDebugEnabled()) {
122: log.debug("JarResourceLoader : trying to load \"" + path
123: + "\"");
124: }
125:
126: // Check path information
127: if (path == null) {
128: log
129: .error("JarResourceLoader : can not load JAR - JAR path is null");
130: }
131: if (!path.startsWith("jar:")) {
132: log
133: .error("JarResourceLoader : JAR path must start with jar: -> "
134: + "see java.net.JarURLConnection for information");
135: }
136: if (!path.endsWith("!/")) {
137: path += "!/";
138: }
139:
140: // Close the jar if it's already open
141: // this is useful for a reload
142: closeJar(path);
143:
144: // Create a new JarHolder
145: JarHolder temp = new JarHolder(rsvc, path);
146: // Add it's entries to the entryCollection
147: addEntries(temp.getEntries());
148: // Add it to the Jar table
149: jarfiles.put(temp.getUrlPath(), temp);
150: }
151:
152: /**
153: * Closes a Jar file and set its URLConnection
154: * to null.
155: */
156: private void closeJar(String path) {
157: if (jarfiles.containsKey(path)) {
158: JarHolder theJar = (JarHolder) jarfiles.get(path);
159: theJar.close();
160: }
161: }
162:
163: /**
164: * Copy all the entries into the entryDirectory
165: * It will overwrite any duplicate keys.
166: */
167: private void addEntries(Hashtable entries) {
168: entryDirectory.putAll(entries);
169: }
170:
171: /**
172: * Get an InputStream so that the Runtime can build a
173: * template with it.
174: *
175: * @param source name of template to get
176: * @return InputStream containing the template
177: * @throws ResourceNotFoundException if template not found
178: * in the file template path.
179: */
180: public InputStream getResourceStream(String source)
181: throws ResourceNotFoundException {
182: InputStream results = null;
183:
184: if (org.apache.commons.lang.StringUtils.isEmpty(source)) {
185: throw new ResourceNotFoundException(
186: "Need to have a resource!");
187: }
188:
189: String normalizedPath = StringUtils.normalizePath(source);
190:
191: if (normalizedPath == null || normalizedPath.length() == 0) {
192: String msg = "JAR resource error : argument "
193: + normalizedPath
194: + " contains .. and may be trying to access "
195: + "content outside of template root. Rejected.";
196:
197: log.error("JarResourceLoader : " + msg);
198:
199: throw new ResourceNotFoundException(msg);
200: }
201:
202: /*
203: * if a / leads off, then just nip that :)
204: */
205: if (normalizedPath.startsWith("/")) {
206: normalizedPath = normalizedPath.substring(1);
207: }
208:
209: if (entryDirectory.containsKey(normalizedPath)) {
210: String jarurl = (String) entryDirectory.get(normalizedPath);
211:
212: if (jarfiles.containsKey(jarurl)) {
213: JarHolder holder = (JarHolder) jarfiles.get(jarurl);
214: results = holder.getResource(normalizedPath);
215: return results;
216: }
217: }
218:
219: throw new ResourceNotFoundException(
220: "JarResourceLoader Error: cannot find resource "
221: + source);
222:
223: }
224:
225: // TODO: SHOULD BE DELEGATED TO THE JARHOLDER
226:
227: /**
228: * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#isSourceModified(org.apache.velocity.runtime.resource.Resource)
229: */
230: public boolean isSourceModified(Resource resource) {
231: return true;
232: }
233:
234: /**
235: * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource)
236: */
237: public long getLastModified(Resource resource) {
238: return 0;
239: }
240: }
|