1 package org.apache.velocity.runtime.resource.loader;
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.InputStream;
23
24 import java.util.Hashtable;
25 import java.util.Vector;
26 import java.util.Map;
27 import java.util.HashMap;
28
29 import org.apache.velocity.util.StringUtils;
30 import org.apache.velocity.runtime.resource.Resource;
31 import org.apache.velocity.exception.ResourceNotFoundException;
32 import org.apache.commons.collections.ExtendedProperties;
33
34 /**
35 * <p>
36 * ResourceLoader to load templates from multiple Jar files.
37 * </p>
38 * <p>
39 * The configuration of the JarResourceLoader is straightforward -
40 * You simply add the JarResourceLoader to the configuration via
41 * </p>
42 * <p><pre>
43 * resource.loader = jar
44 * jar.resource.loader.class = org.apache.velocity.runtime.resource.loader.JarResourceLoader
45 * jar.resource.loader.path = list of JAR <URL>s
46 * </pre></p>
47 *
48 * <p> So for example, if you had a jar file on your local filesystem, you could simply do
49 * <pre>
50 * jar.resource.loader.path = jar:file:/opt/myfiles/jar1.jar
51 * </pre>
52 * </p>
53 * <p> Note that jar specification for the <code>.path</code> configuration property
54 * conforms to the same rules for the java.net.JarUrlConnection class.
55 * </p>
56 *
57 * <p> For a working example, see the unit test case,
58 * org.apache.velocity.test.MultiLoaderTestCase class
59 * </p>
60 *
61 * @author <a href="mailto:mailmur@yahoo.com">Aki Nieminen</a>
62 * @author <a href="mailto:daveb@miceda-data.com">Dave Bryson</a>
63 * @version $Id: JarResourceLoader.java 1043077 2010-12-07 15:01:25Z apetrelli $
64 */
65 public class JarResourceLoader extends ResourceLoader
66 {
67 /**
68 * Maps entries to the parent JAR File
69 * Key = the entry *excluding* plain directories
70 * Value = the JAR URL
71 */
72 private Map entryDirectory = new HashMap(559);
73
74 /**
75 * Maps JAR URLs to the actual JAR
76 * Key = the JAR URL
77 * Value = the JAR
78 */
79 private Map jarfiles = new HashMap(89);
80
81 /**
82 * Called by Velocity to initialize the loader
83 * @param configuration
84 */
85 public void init( ExtendedProperties configuration)
86 {
87 log.trace("JarResourceLoader : initialization starting.");
88
89 // rest of Velocity engine still use legacy Vector
90 // and Hashtable classes. Classes are implicitly
91 // synchronized even if we don't need it.
92 Vector paths = configuration.getVector("path");
93 StringUtils.trimStrings(paths);
94
95 if (paths != null)
96 {
97 log.debug("JarResourceLoader # of paths : " + paths.size() );
98
99 for ( int i=0; i<paths.size(); i++ )
100 {
101 loadJar( (String)paths.get(i) );
102 }
103 }
104
105 log.trace("JarResourceLoader : initialization complete.");
106 }
107
108 private void loadJar( String path )
109 {
110 if (log.isDebugEnabled())
111 {
112 log.debug("JarResourceLoader : trying to load \"" + path + "\"");
113 }
114
115 // Check path information
116 if ( path == null )
117 {
118 String msg = "JarResourceLoader : can not load JAR - JAR path is null";
119 log.error(msg);
120 throw new RuntimeException(msg);
121 }
122 if ( !path.startsWith("jar:") )
123 {
124 String msg = "JarResourceLoader : JAR path must start with jar: -> see java.net.JarURLConnection for information";
125 log.error(msg);
126 throw new RuntimeException(msg);
127 }
128 if ( path.indexOf("!/") < 0 )
129 {
130 path += "!/";
131 }
132
133 // Close the jar if it's already open
134 // this is useful for a reload
135 closeJar( path );
136
137 // Create a new JarHolder
138 JarHolder temp = new JarHolder( rsvc, path );
139 // Add it's entries to the entryCollection
140 addEntries( temp.getEntries() );
141 // Add it to the Jar table
142 jarfiles.put( temp.getUrlPath(), temp );
143 }
144
145 /**
146 * Closes a Jar file and set its URLConnection
147 * to null.
148 */
149 private void closeJar( String path )
150 {
151 if ( jarfiles.containsKey(path) )
152 {
153 JarHolder theJar = (JarHolder)jarfiles.get(path);
154 theJar.close();
155 }
156 }
157
158 /**
159 * Copy all the entries into the entryDirectory
160 * It will overwrite any duplicate keys.
161 */
162 private void addEntries( Hashtable entries )
163 {
164 entryDirectory.putAll( entries );
165 }
166
167 /**
168 * Get an InputStream so that the Runtime can build a
169 * template with it.
170 *
171 * @param source name of template to get
172 * @return InputStream containing the template
173 * @throws ResourceNotFoundException if template not found
174 * in the file template path.
175 */
176 public InputStream getResourceStream( String source )
177 throws ResourceNotFoundException
178 {
179 InputStream results = null;
180
181 if (org.apache.commons.lang.StringUtils.isEmpty(source))
182 {
183 throw new ResourceNotFoundException("Need to have a resource!");
184 }
185
186 String normalizedPath = StringUtils.normalizePath( source );
187
188 if ( normalizedPath == null || normalizedPath.length() == 0 )
189 {
190 String msg = "JAR resource error : argument " + normalizedPath +
191 " contains .. and may be trying to access " +
192 "content outside of template root. Rejected.";
193
194 log.error( "JarResourceLoader : " + msg );
195
196 throw new ResourceNotFoundException ( msg );
197 }
198
199 /*
200 * if a / leads off, then just nip that :)
201 */
202 if ( normalizedPath.startsWith("/") )
203 {
204 normalizedPath = normalizedPath.substring(1);
205 }
206
207 if ( entryDirectory.containsKey( normalizedPath ) )
208 {
209 String jarurl = (String)entryDirectory.get( normalizedPath );
210
211 if ( jarfiles.containsKey( jarurl ) )
212 {
213 JarHolder holder = (JarHolder)jarfiles.get( jarurl );
214 results = holder.getResource( normalizedPath );
215 return results;
216 }
217 }
218
219 throw new ResourceNotFoundException( "JarResourceLoader Error: cannot find resource " +
220 source );
221
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 {
232 return true;
233 }
234
235 /**
236 * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource)
237 */
238 public long getLastModified(Resource resource)
239 {
240 return 0;
241 }
242 }