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><loader-id>.resource.loader.<property> = <value></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 }