View Javadoc

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 &lt;URL&gt;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 691884 2008-09-04 06:46:51Z nbubna $
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          /*
96           *  support the old version but deprecate with a log message
97           */
98  
99          if( paths == null || paths.size() == 0)
100         {
101             paths = configuration.getVector("resource.path");
102             StringUtils.trimStrings(paths);
103 
104             if (paths != null && paths.size() > 0)
105             {
106                 log.debug("JarResourceLoader : you are using a deprecated configuration"
107                           + " property for the JarResourceLoader -> '<name>.resource.loader.resource.path'."
108                           + " Please change to the conventional '<name>.resource.loader.path'.");
109             }
110         }
111 
112         if (paths != null)
113         {
114             log.debug("JarResourceLoader # of paths : " + paths.size() );
115 
116             for ( int i=0; i<paths.size(); i++ )
117             {
118                 loadJar( (String)paths.get(i) );
119             }
120         }
121 
122         log.trace("JarResourceLoader : initialization complete.");
123     }
124 
125     private void loadJar( String path )
126     {
127         if (log.isDebugEnabled())
128         {
129             log.debug("JarResourceLoader : trying to load \"" + path + "\"");
130         }
131 
132         // Check path information
133         if ( path == null )
134         {
135             String msg = "JarResourceLoader : can not load JAR - JAR path is null";
136             log.error(msg);
137             throw new RuntimeException(msg);
138         }
139         if ( !path.startsWith("jar:") )
140         {
141             String msg = "JarResourceLoader : JAR path must start with jar: -> see java.net.JarURLConnection for information";
142             log.error(msg);
143             throw new RuntimeException(msg);
144         }
145         if ( path.indexOf("!/") < 0 )
146         {
147             path += "!/";
148         }
149 
150         // Close the jar if it's already open
151         // this is useful for a reload
152         closeJar( path );
153 
154         // Create a new JarHolder
155         JarHolder temp = new JarHolder( rsvc,  path );
156         // Add it's entries to the entryCollection
157         addEntries( temp.getEntries() );
158         // Add it to the Jar table
159         jarfiles.put( temp.getUrlPath(), temp );
160     }
161 
162     /**
163      * Closes a Jar file and set its URLConnection
164      * to null.
165      */
166     private void closeJar( String path )
167     {
168         if ( jarfiles.containsKey(path) )
169         {
170             JarHolder theJar = (JarHolder)jarfiles.get(path);
171             theJar.close();
172         }
173     }
174 
175     /**
176      * Copy all the entries into the entryDirectory
177      * It will overwrite any duplicate keys.
178      */
179     private void addEntries( Hashtable entries )
180     {
181         entryDirectory.putAll( entries );
182     }
183 
184     /**
185      * Get an InputStream so that the Runtime can build a
186      * template with it.
187      *
188      * @param source name of template to get
189      * @return InputStream containing the template
190      * @throws ResourceNotFoundException if template not found
191      *         in the file template path.
192      */
193     public InputStream getResourceStream( String source )
194         throws ResourceNotFoundException
195     {
196         InputStream results = null;
197 
198         if (org.apache.commons.lang.StringUtils.isEmpty(source))
199         {
200             throw new ResourceNotFoundException("Need to have a resource!");
201         }
202 
203         String normalizedPath = StringUtils.normalizePath( source );
204 
205         if ( normalizedPath == null || normalizedPath.length() == 0 )
206         {
207             String msg = "JAR resource error : argument " + normalizedPath +
208                 " contains .. and may be trying to access " +
209                 "content outside of template root.  Rejected.";
210 
211             log.error( "JarResourceLoader : " + msg );
212 
213             throw new ResourceNotFoundException ( msg );
214         }
215 
216         /*
217          *  if a / leads off, then just nip that :)
218          */
219         if ( normalizedPath.startsWith("/") )
220         {
221             normalizedPath = normalizedPath.substring(1);
222         }
223 
224         if ( entryDirectory.containsKey( normalizedPath ) )
225         {
226             String jarurl  = (String)entryDirectory.get( normalizedPath );
227 
228             if ( jarfiles.containsKey( jarurl ) )
229             {
230                 JarHolder holder = (JarHolder)jarfiles.get( jarurl );
231                 results =  holder.getResource( normalizedPath );
232                 return results;
233             }
234         }
235 
236         throw new ResourceNotFoundException( "JarResourceLoader Error: cannot find resource " +
237           source );
238 
239     }
240 
241 
242     // TODO: SHOULD BE DELEGATED TO THE JARHOLDER
243 
244     /**
245      * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#isSourceModified(org.apache.velocity.runtime.resource.Resource)
246      */
247     public boolean isSourceModified(Resource resource)
248     {
249         return true;
250     }
251 
252     /**
253      * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource)
254      */
255     public long getLastModified(Resource resource)
256     {
257         return 0;
258     }
259 }