• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 org.apache.velocity.exception.ResourceNotFoundException;
23 import org.apache.velocity.exception.VelocityException;
24 import org.apache.velocity.runtime.RuntimeConstants;
25 import org.apache.velocity.runtime.resource.Resource;
26 import org.apache.velocity.util.ExtProperties;
27 
28 import org.apache.commons.io.FilenameUtils;
29 import org.apache.commons.lang3.StringUtils;
30 
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.Reader;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.ListIterator;
37 import java.util.Map;
38 
39 /**
40  * <p>
41  * ResourceLoader to load templates from multiple Jar files.
42  * </p>
43  * <p>
44  * The configuration of the JarResourceLoader is straightforward -
45  * You simply add the JarResourceLoader to the configuration via
46  * </p>
47  * <pre><code>
48  *    resource.loaders = jar
49  *    resource.loader.jar.class = org.apache.velocity.runtime.resource.loader.JarResourceLoader
50  *    resource.loader.jar.path = list of JAR &lt;URL&gt;s
51  * </code></pre>
52  *
53  * <p> So for example, if you had a jar file on your local filesystem, you could simply do</p>
54  *    <pre><code>
55  *    resource.loader.jar.path = jar:file:/opt/myfiles/jar1.jar
56  *    </code></pre>
57  * <p> Note that jar specification for the <code>.path</code> configuration property
58  * conforms to the same rules for the java.net.JarUrlConnection class.
59  * </p>
60  *
61  * <p> For a working example, see the unit test case,
62  *  org.apache.velocity.test.MultiLoaderTestCase class
63  * </p>
64  *
65  * @author <a href="mailto:mailmur@yahoo.com">Aki Nieminen</a>
66  * @author <a href="mailto:daveb@miceda-data.com">Dave Bryson</a>
67  * @version $Id$
68  */
69 public class JarResourceLoader extends ResourceLoader
70 {
71     /**
72      * Maps entries to the parent JAR File
73      * Key = the entry *excluding* plain directories
74      * Value = the JAR URL
75      */
76     private Map<String, String> entryDirectory = new HashMap<>(559);
77 
78     /**
79      * Maps JAR URLs to the actual JAR
80      * Key = the JAR URL
81      * Value = the JAR
82      */
83     private Map<String, JarHolder> jarfiles = new HashMap<>(89);
84 
85     /**
86      * Called by Velocity to initialize the loader
87      * @param configuration
88      */
89     @Override
init(ExtProperties configuration)90     public void init(ExtProperties configuration)
91     {
92         log.trace("JarResourceLoader: initialization starting.");
93 
94         List<String> paths = configuration.getList(RuntimeConstants.RESOURCE_LOADER_PATHS);
95 
96         if (paths != null)
97         {
98             log.debug("JarResourceLoader # of paths: {}", paths.size() );
99 
100             for (ListIterator<String> it = paths.listIterator(); it.hasNext(); )
101             {
102                 String jar = StringUtils.trim(it.next());
103                 it.set(jar);
104                 loadJar(jar);
105             }
106         }
107 
108         log.trace("JarResourceLoader: initialization complete.");
109     }
110 
loadJar( String path )111     private void loadJar( String path )
112     {
113         log.debug("JarResourceLoader: trying to load \"{}\"", path);
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.contains("!/"))
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, log );
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      */
closeJar( String path )149     private void closeJar( String path )
150     {
151         if ( jarfiles.containsKey(path) )
152         {
153             JarHolder theJar = 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      */
addEntries( Map<String, String> entries )162     private void addEntries( Map<String, String> entries )
163     {
164         entryDirectory.putAll( entries );
165     }
166 
167     /**
168      * Get a Reader so that the Runtime can build a
169      * template with it.
170      *
171      * @param source name of template to get
172      * @param encoding asked encoding
173      * @return InputStream containing the template
174      * @throws ResourceNotFoundException if template not found
175      *         in the file template path.
176      * @since 2.0
177     */
178     @Override
getResourceReader(String source, String encoding )179     public Reader getResourceReader(String source, String encoding )
180             throws ResourceNotFoundException
181     {
182         Reader result = null;
183 
184         if (org.apache.commons.lang3.StringUtils.isEmpty(source))
185         {
186             throw new ResourceNotFoundException("Need to have a resource!");
187         }
188 
189         String normalizedPath = FilenameUtils.normalize( source, true );
190 
191         if ( normalizedPath == null || normalizedPath.length() == 0 )
192         {
193             String msg = "JAR resource error: argument " + 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         {
207             normalizedPath = normalizedPath.substring(1);
208         }
209 
210         if ( entryDirectory.containsKey( normalizedPath ) )
211         {
212             String jarurl  = entryDirectory.get( normalizedPath );
213 
214             if ( jarfiles.containsKey( jarurl ) )
215             {
216                 JarHolder holder = (JarHolder)jarfiles.get( jarurl );
217                 InputStream rawStream = holder.getResource( normalizedPath );
218                 try
219                 {
220                     return buildReader(rawStream, encoding);
221                 }
222                 catch (Exception e)
223                 {
224                     if (rawStream != null)
225                     {
226                         try
227                         {
228                             rawStream.close();
229                         }
230                         catch (IOException ioe) {}
231                     }
232                     String msg = "JAR resource error: Exception while loading " + source;
233                     log.error(msg, e);
234                     throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace());
235                 }
236             }
237         }
238 
239         throw new ResourceNotFoundException( "JarResourceLoader Error: cannot find resource " +
240                 source );
241 
242     }
243 
244     // TODO: SHOULD BE DELEGATED TO THE JARHOLDER
245 
246     /**
247      * @see ResourceLoader#isSourceModified(org.apache.velocity.runtime.resource.Resource)
248      */
249     @Override
isSourceModified(Resource resource)250     public boolean isSourceModified(Resource resource)
251     {
252         return true;
253     }
254 
255     /**
256      * @see ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource)
257      */
258     @Override
getLastModified(Resource resource)259     public long getLastModified(Resource resource)
260     {
261         return 0;
262     }
263 }
264