View Javadoc

1   package org.apache.velocity.runtime.resource;
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.IOException;
23  import java.io.InputStream;
24  import java.util.ArrayList;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Vector;
28  
29  import org.apache.commons.collections.ExtendedProperties;
30  import org.apache.velocity.exception.ParseErrorException;
31  import org.apache.velocity.exception.ResourceNotFoundException;
32  import org.apache.velocity.runtime.RuntimeConstants;
33  import org.apache.velocity.runtime.RuntimeServices;
34  import org.apache.velocity.runtime.log.Log;
35  import org.apache.velocity.runtime.resource.loader.ResourceLoader;
36  import org.apache.velocity.runtime.resource.loader.ResourceLoaderFactory;
37  import org.apache.velocity.util.ClassUtils;
38  import org.apache.velocity.util.StringUtils;
39  
40  
41  /**
42   * Class to manage the text resource for the Velocity Runtime.
43   *
44   * @author  <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
45   * @author  <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
46   * @author  <a href="mailto:paulo.gaspar@krankikom.de">Paulo Gaspar</a>
47   * @author  <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
48   * @author  <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
49   * @version  $Id: ResourceManagerImpl.java 490011 2006-12-24 12:19:09Z henning $
50   */
51  public class ResourceManagerImpl
52      implements ResourceManager
53  {
54  
55      /** A template resources. */
56      public static final int RESOURCE_TEMPLATE = 1;
57  
58      /** A static content resource. */
59      public static final int RESOURCE_CONTENT = 2;
60  
61      /** token used to identify the loader internally. */
62      private static final String RESOURCE_LOADER_IDENTIFIER = "_RESOURCE_LOADER_IDENTIFIER_";
63  
64      /** Object implementing ResourceCache to be our resource manager's Resource cache. */
65      protected ResourceCache globalCache = null;
66  
67      /** The List of templateLoaders that the Runtime will use to locate the InputStream source of a template. */
68      protected final List resourceLoaders = new ArrayList();
69  
70      /**
71       * This is a list of the template input stream source initializers, basically properties for a particular template stream
72       * source. The order in this list reflects numbering of the properties i.e.
73       *
74       * <p>&lt;loader-id&gt;.resource.loader.&lt;property&gt; = &lt;value&gt;</p>
75       */
76      private final List sourceInitializerList = new ArrayList();
77  
78      /**
79       * Has this Manager been initialized?
80       */
81      private boolean isInit = false;
82  
83      /** switch to turn off log notice when a resource is found for the first time. */
84      private boolean logWhenFound = true;
85  
86      /** The internal RuntimeServices object. */
87      protected RuntimeServices rsvc = null;
88  
89      /** Logging. */
90      protected Log log = null;
91  
92      /**
93       * Initialize the ResourceManager.
94       *
95       * @param  rsvc  The Runtime Services object which is associated with this Resource Manager.
96       *
97       * @throws  Exception
98       */
99      public synchronized void initialize(final RuntimeServices rsvc)
100         throws Exception
101     {
102 	if (isInit)
103 	{
104 	    log.warn("Re-initialization of ResourceLoader attempted!");
105 	    return;
106 	}
107 	
108         ResourceLoader resourceLoader = null;
109 
110         this.rsvc = rsvc;
111         log = rsvc.getLog();
112 
113         log.debug("Default ResourceManager initializing. (" + this.getClass() + ")");
114 
115         assembleResourceLoaderInitializers();
116 
117         for (Iterator it = sourceInitializerList.iterator(); it.hasNext();)
118         {
119             /**
120              * Resource loader can be loaded either via class name or be passed
121              * in as an instance.
122              */
123             ExtendedProperties configuration = (ExtendedProperties) it.next();
124 
125             String loaderClass = StringUtils.nullTrim(configuration.getString("class"));
126             ResourceLoader loaderInstance = (ResourceLoader) configuration.get("instance");
127 
128             if (loaderInstance != null)
129             {
130                 resourceLoader = loaderInstance;
131             }
132             else if (loaderClass != null)
133             {
134                 resourceLoader = ResourceLoaderFactory.getLoader(rsvc, loaderClass);
135             }
136             else
137             {
138                 log.error("Unable to find '" +
139                           configuration.getString(RESOURCE_LOADER_IDENTIFIER) +
140                           ".resource.loader.class' specification in configuration." +
141                           " This is a critical value.  Please adjust configuration.");
142 
143                 continue; // for(...
144             }
145 
146             resourceLoader.commonInit(rsvc, configuration);
147             resourceLoader.init(configuration);
148             resourceLoaders.add(resourceLoader);
149         }
150 
151         /*
152          * now see if this is overridden by configuration
153          */
154 
155         logWhenFound = rsvc.getBoolean(RuntimeConstants.RESOURCE_MANAGER_LOGWHENFOUND, true);
156 
157         /*
158          *  now, is a global cache specified?
159          */
160 
161         String cacheClassName = rsvc.getString(RuntimeConstants.RESOURCE_MANAGER_CACHE_CLASS);
162 
163         Object cacheObject = null;
164 
165         if (org.apache.commons.lang.StringUtils.isNotEmpty(cacheClassName))
166         {
167 
168             try
169             {
170                 cacheObject = ClassUtils.getNewInstance(cacheClassName);
171             }
172             catch (ClassNotFoundException cnfe)
173             {
174                 log.error("The specified class for ResourceCache (" + cacheClassName +
175                           ") does not exist or is not accessible to the current classloader.");
176                 cacheObject = null;
177             }
178 
179             if (!(cacheObject instanceof ResourceCache))
180             {
181                 log.error("The specified class for ResourceCache (" + cacheClassName +
182                           ") does not implement " + ResourceCache.class.getName() +
183                           " ResourceManager. Using default ResourceCache implementation.");
184                 cacheObject = null;
185             }
186         }
187 
188         /*
189          *  if we didn't get through that, just use the default.
190          */
191         if (cacheObject == null)
192         {
193             cacheObject = new ResourceCacheImpl();
194         }
195 
196         globalCache = (ResourceCache) cacheObject;
197 
198         globalCache.initialize(rsvc);
199 
200         log.trace("Default ResourceManager initialization complete.");
201     }
202 
203     /**
204      * This will produce a List of Hashtables, each hashtable contains the intialization info for a particular resource loader. This
205      * Hashtable will be passed in when initializing the the template loader.
206      */
207     private void assembleResourceLoaderInitializers()
208     {
209         Vector resourceLoaderNames = rsvc.getConfiguration().getVector(RuntimeConstants.RESOURCE_LOADER);
210         StringUtils.trimStrings(resourceLoaderNames);
211 
212         for (Iterator it = resourceLoaderNames.iterator(); it.hasNext(); )
213         {
214 
215             /*
216              * The loader id might look something like the following:
217              *
218              * file.resource.loader
219              *
220              * The loader id is the prefix used for all properties
221              * pertaining to a particular loader.
222              */
223             String loaderName = (String) it.next();
224             StringBuffer loaderID = new StringBuffer(loaderName);
225             loaderID.append(".").append(RuntimeConstants.RESOURCE_LOADER);
226 
227             ExtendedProperties loaderConfiguration = 
228         		rsvc.getConfiguration().subset(loaderID.toString());
229 
230             /*
231              *  we can't really count on ExtendedProperties to give us an empty set
232              */
233 
234             if (loaderConfiguration == null)
235             {
236                 log.warn("ResourceManager : No configuration information for resource loader named '" +
237                          loaderName + "'. Skipping.");
238 
239                 continue;
240             }
241 
242             /*
243              *  add the loader name token to the initializer if we need it
244              *  for reference later. We can't count on the user to fill
245              *  in the 'name' field
246              */
247 
248             loaderConfiguration.setProperty(RESOURCE_LOADER_IDENTIFIER, loaderName);
249 
250             /*
251              * Add resources to the list of resource loader
252              * initializers.
253              */
254             sourceInitializerList.add(loaderConfiguration);
255         }
256     }
257 
258     /**
259      * Gets the named resource. Returned class type corresponds to specified type (i.e. <code>Template</code> to <code>
260      * RESOURCE_TEMPLATE</code>).
261      *
262      * @param  resourceName  The name of the resource to retrieve.
263      * @param  resourceType  The type of resource (<code>RESOURCE_TEMPLATE</code>, <code>RESOURCE_CONTENT</code>, etc.).
264      * @param  encoding  The character encoding to use.
265      *
266      * @return  Resource with the template parsed and ready.
267      *
268      * @throws  ResourceNotFoundException  if template not found from any available source.
269      * @throws  ParseErrorException  if template cannot be parsed due to syntax (or other) error.
270      * @throws  Exception  if a problem in parse
271      */
272     public synchronized Resource getResource(final String resourceName, final int resourceType, final String encoding)
273         throws ResourceNotFoundException,
274             ParseErrorException,
275             Exception
276     {
277         /*
278          * Check to see if the resource was placed in the cache.
279          * If it was placed in the cache then we will use
280          * the cached version of the resource. If not we
281          * will load it.
282          *
283          * Note: the type is included in the key to differentiate ContentResource
284          * (static content from #include) with a Template.
285          */
286 
287         String resourceKey = resourceType + resourceName;
288         Resource resource = globalCache.get(resourceKey);
289 
290         if (resource != null)
291         {
292             /*
293              *  refresh the resource
294              */
295 
296             try
297             {
298                 refreshResource(resource, encoding);
299             }
300             catch (ResourceNotFoundException rnfe)
301             {
302                 /*
303                  *  something exceptional happened to that resource
304                  *  this could be on purpose,
305                  *  so clear the cache and try again
306                  */
307 
308                 globalCache.remove(resourceKey);
309 
310                 return getResource(resourceName, resourceType, encoding);
311             }
312             catch (ParseErrorException pee)
313             {
314                 log.error("ResourceManager.getResource() exception", pee);
315                 throw pee;
316             }
317             catch (RuntimeException re)
318             {
319         	throw re;
320             }
321             catch (Exception e)
322             {
323                 log.error("ResourceManager.getResource() exception", e);
324                 throw e;
325             }
326         }
327         else
328         {
329             try
330             {
331                 /*
332                  *  it's not in the cache, so load it.
333                  */
334 
335                 resource = loadResource(resourceName, resourceType, encoding);
336 
337                 if (resource.getResourceLoader().isCachingOn())
338                 {
339                     globalCache.put(resourceKey, resource);
340                 }
341             }
342             catch (ResourceNotFoundException rnfe)
343             {
344                 log.error("ResourceManager : unable to find resource '" +
345                           resourceName + "' in any resource loader.");
346                 throw rnfe;
347             }
348             catch (ParseErrorException pee)
349             {
350                 log.error("ResourceManager.getResource() parse exception", pee);
351                 throw pee;
352             }
353             catch (RuntimeException re)
354             {
355     		throw re;
356             }
357             catch (Exception e)
358             {
359                 log.error("ResourceManager.getResource() exception new", e);
360                 throw e;
361             }
362         }
363 
364         return resource;
365     }
366 
367     /**
368      * Loads a resource from the current set of resource loaders.
369      *
370      * @param  resourceName  The name of the resource to retrieve.
371      * @param  resourceType  The type of resource (<code>RESOURCE_TEMPLATE</code>, <code>RESOURCE_CONTENT</code>, etc.).
372      * @param  encoding  The character encoding to use.
373      *
374      * @return  Resource with the template parsed and ready.
375      *
376      * @throws  ResourceNotFoundException  if template not found from any available source.
377      * @throws  ParseErrorException  if template cannot be parsed due to syntax (or other) error.
378      * @throws  Exception  if a problem in parse
379      */
380     protected Resource loadResource(String resourceName, int resourceType, String encoding)
381         throws ResourceNotFoundException,
382             ParseErrorException,
383             Exception
384     {
385         Resource resource = ResourceFactory.getResource(resourceName, resourceType);
386 
387         resource.setRuntimeServices(rsvc);
388 
389         resource.setName(resourceName);
390         resource.setEncoding(encoding);
391 
392         /*
393          * Now we have to try to find the appropriate
394          * loader for this resource. We have to cycle through
395          * the list of available resource loaders and see
396          * which one gives us a stream that we can use to
397          * make a resource with.
398          */
399 
400         long howOldItWas = 0;
401 
402         for (Iterator it = resourceLoaders.iterator(); it.hasNext();)
403         {
404             ResourceLoader resourceLoader = (ResourceLoader) it.next();
405             resource.setResourceLoader(resourceLoader);
406 
407             /*
408              *  catch the ResourceNotFound exception
409              *  as that is ok in our new multi-loader environment
410              */
411 
412             try
413             {
414 
415                 if (resource.process())
416                 {
417                     /*
418                      *  FIXME  (gmj)
419                      *  moved in here - technically still
420                      *  a problem - but the resource needs to be
421                      *  processed before the loader can figure
422                      *  it out due to to the new
423                      *  multi-path support - will revisit and fix
424                      */
425 
426                     if (logWhenFound && log.isDebugEnabled())
427                     {
428                         log.debug("ResourceManager : found " + resourceName +
429                                   " with loader " +
430                                   resourceLoader.getClassName());
431                     }
432 
433                     howOldItWas = resourceLoader.getLastModified(resource);
434 
435                     break;
436                 }
437             }
438             catch (ResourceNotFoundException rnfe)
439             {
440                 /*
441                  *  that's ok - it's possible to fail in
442                  *  multi-loader environment
443                  */
444             }
445         }
446 
447         /*
448          * Return null if we can't find a resource.
449          */
450         if (resource.getData() == null)
451         {
452             throw new ResourceNotFoundException("Unable to find resource '" + resourceName + "'");
453         }
454 
455         /*
456          *  some final cleanup
457          */
458 
459         resource.setLastModified(howOldItWas);
460         resource.setModificationCheckInterval(resource.getResourceLoader().getModificationCheckInterval());
461 
462         resource.touch();
463 
464         return resource;
465     }
466 
467     /**
468      * Takes an existing resource, and 'refreshes' it. This generally means that the source of the resource is checked for changes
469      * according to some cache/check algorithm and if the resource changed, then the resource data is reloaded and re-parsed.
470      *
471      * @param  resource  resource to refresh
472      * @param  encoding  character encoding of the resource to refresh.
473      *
474      * @throws  ResourceNotFoundException  if template not found from current source for this Resource
475      * @throws  ParseErrorException  if template cannot be parsed due to syntax (or other) error.
476      * @throws  Exception  if a problem in parse
477      */
478     protected void refreshResource(final Resource resource, final String encoding)
479         throws ResourceNotFoundException,
480             ParseErrorException,
481             Exception
482     {
483 
484         /*
485          * The resource knows whether it needs to be checked
486          * or not, and the resource's loader can check to
487          * see if the source has been modified. If both
488          * these conditions are true then we must reload
489          * the input stream and parse it to make a new
490          * AST for the resource.
491          */
492         if (resource.requiresChecking())
493         {
494             /*
495              *  touch() the resource to reset the counters
496              */
497 
498             resource.touch();
499 
500             if (resource.isSourceModified())
501             {
502                 /*
503                  *  now check encoding info.  It's possible that the newly declared
504                  *  encoding is different than the encoding already in the resource
505                  *  this strikes me as bad...
506                  */
507 
508                 if (!org.apache.commons.lang.StringUtils.equals(resource.getEncoding(), encoding))
509                 {
510                     log.warn("Declared encoding for template '" +
511                              resource.getName() +
512                              "' is different on reload. Old = '" +
513                              resource.getEncoding() + "' New = '" + encoding);
514 
515                     resource.setEncoding(encoding);
516                 }
517 
518                 /*
519                  *  read how old the resource is _before_
520                  *  processing (=>reading) it
521                  */
522                 long howOldItWas = resource.getResourceLoader().getLastModified(resource);
523 
524                 /*
525                  *  read in the fresh stream and parse
526                  */
527 
528                 resource.process();
529 
530                 /*
531                  *  now set the modification info and reset
532                  *  the modification check counters
533                  */
534 
535                 resource.setLastModified(howOldItWas);
536             }
537         }
538     }
539 
540     /**
541      * Gets the named resource. Returned class type corresponds to specified type (i.e. <code>Template</code> to <code>
542      * RESOURCE_TEMPLATE</code>).
543      *
544      * @param  resourceName  The name of the resource to retrieve.
545      * @param  resourceType  The type of resource (<code>RESOURCE_TEMPLATE</code>, <code>RESOURCE_CONTENT</code>, etc.).
546      *
547      * @return  Resource with the template parsed and ready.
548      *
549      * @throws  ResourceNotFoundException  if template not found from any available source.
550      * @throws  ParseErrorException  if template cannot be parsed due to syntax (or other) error.
551      * @throws  Exception  if a problem in parse
552      *
553      * @deprecated  Use {@link #getResource(String resourceName, int resourceType, String encoding )}
554      */
555     public Resource getResource(String resourceName, int resourceType)
556         throws ResourceNotFoundException,
557             ParseErrorException,
558             Exception
559     {
560         return getResource(resourceName, resourceType, RuntimeConstants.ENCODING_DEFAULT);
561     }
562 
563     /**
564      * Determines if a template exists, and returns name of the loader that provides it. This is a slightly less hokey way to
565      * support the Velocity.templateExists() utility method, which was broken when per-template encoding was introduced. We can
566      * revisit this.
567      *
568      * @param  resourceName  Name of template or content resource
569      *
570      * @return  class name of loader than can provide it
571      */
572     public String getLoaderNameForResource(String resourceName)
573     {
574         /*
575          *  loop through our loaders...
576          */
577         for (Iterator it = resourceLoaders.iterator(); it.hasNext(); )
578         {
579             ResourceLoader resourceLoader = (ResourceLoader) it.next();
580 
581             InputStream is = null;
582 
583             /*
584              *  if we find one that can provide the resource,
585              *  return the name of the loaders's Class
586              */
587             try
588             {
589                 is = resourceLoader.getResourceStream(resourceName);
590 
591                 if (is != null)
592                 {
593                     return resourceLoader.getClass().toString();
594                 }
595             }
596             catch (ResourceNotFoundException rnfe)
597             {
598                 /*
599                  * this isn't a problem.  keep going
600                  */
601             }
602             finally
603             {
604 
605                 /*
606                  *  if we did find one, clean up because we were
607                  *  returned an open stream
608                  */
609                 if (is != null)
610                 {
611 
612                     try
613                     {
614                         is.close();
615                     }
616                     catch (IOException supressed)
617                     { }
618                 }
619             }
620         }
621 
622         return null;
623     }
624 }