001: package org.apache.velocity.runtime.resource;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.util.ArrayList;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.Vector;
028:
029: import org.apache.commons.collections.ExtendedProperties;
030: import org.apache.velocity.exception.ParseErrorException;
031: import org.apache.velocity.exception.ResourceNotFoundException;
032: import org.apache.velocity.runtime.RuntimeConstants;
033: import org.apache.velocity.runtime.RuntimeServices;
034: import org.apache.velocity.runtime.log.Log;
035: import org.apache.velocity.runtime.resource.loader.ResourceLoader;
036: import org.apache.velocity.runtime.resource.loader.ResourceLoaderFactory;
037: import org.apache.velocity.util.ClassUtils;
038: import org.apache.velocity.util.StringUtils;
039:
040: /**
041: * Class to manage the text resource for the Velocity Runtime.
042: *
043: * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
044: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
045: * @author <a href="mailto:paulo.gaspar@krankikom.de">Paulo Gaspar</a>
046: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
047: * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
048: * @version $Id: ResourceManagerImpl.java 490011 2006-12-24 12:19:09Z henning $
049: */
050: public class ResourceManagerImpl implements ResourceManager {
051:
052: /** A template resources. */
053: public static final int RESOURCE_TEMPLATE = 1;
054:
055: /** A static content resource. */
056: public static final int RESOURCE_CONTENT = 2;
057:
058: /** token used to identify the loader internally. */
059: private static final String RESOURCE_LOADER_IDENTIFIER = "_RESOURCE_LOADER_IDENTIFIER_";
060:
061: /** Object implementing ResourceCache to be our resource manager's Resource cache. */
062: protected ResourceCache globalCache = null;
063:
064: /** The List of templateLoaders that the Runtime will use to locate the InputStream source of a template. */
065: protected final List resourceLoaders = new ArrayList();
066:
067: /**
068: * This is a list of the template input stream source initializers, basically properties for a particular template stream
069: * source. The order in this list reflects numbering of the properties i.e.
070: *
071: * <p><loader-id>.resource.loader.<property> = <value></p>
072: */
073: private final List sourceInitializerList = new ArrayList();
074:
075: /**
076: * Has this Manager been initialized?
077: */
078: private boolean isInit = false;
079:
080: /** switch to turn off log notice when a resource is found for the first time. */
081: private boolean logWhenFound = true;
082:
083: /** The internal RuntimeServices object. */
084: protected RuntimeServices rsvc = null;
085:
086: /** Logging. */
087: protected Log log = null;
088:
089: /**
090: * Initialize the ResourceManager.
091: *
092: * @param rsvc The Runtime Services object which is associated with this Resource Manager.
093: *
094: * @throws Exception
095: */
096: public synchronized void initialize(final RuntimeServices rsvc)
097: throws Exception {
098: if (isInit) {
099: log.warn("Re-initialization of ResourceLoader attempted!");
100: return;
101: }
102:
103: ResourceLoader resourceLoader = null;
104:
105: this .rsvc = rsvc;
106: log = rsvc.getLog();
107:
108: log.debug("Default ResourceManager initializing. ("
109: + this .getClass() + ")");
110:
111: assembleResourceLoaderInitializers();
112:
113: for (Iterator it = sourceInitializerList.iterator(); it
114: .hasNext();) {
115: /**
116: * Resource loader can be loaded either via class name or be passed
117: * in as an instance.
118: */
119: ExtendedProperties configuration = (ExtendedProperties) it
120: .next();
121:
122: String loaderClass = StringUtils.nullTrim(configuration
123: .getString("class"));
124: ResourceLoader loaderInstance = (ResourceLoader) configuration
125: .get("instance");
126:
127: if (loaderInstance != null) {
128: resourceLoader = loaderInstance;
129: } else if (loaderClass != null) {
130: resourceLoader = ResourceLoaderFactory.getLoader(rsvc,
131: loaderClass);
132: } else {
133: log
134: .error("Unable to find '"
135: + configuration
136: .getString(RESOURCE_LOADER_IDENTIFIER)
137: + ".resource.loader.class' specification in configuration."
138: + " This is a critical value. Please adjust configuration.");
139:
140: continue; // for(...
141: }
142:
143: resourceLoader.commonInit(rsvc, configuration);
144: resourceLoader.init(configuration);
145: resourceLoaders.add(resourceLoader);
146: }
147:
148: /*
149: * now see if this is overridden by configuration
150: */
151:
152: logWhenFound = rsvc.getBoolean(
153: RuntimeConstants.RESOURCE_MANAGER_LOGWHENFOUND, true);
154:
155: /*
156: * now, is a global cache specified?
157: */
158:
159: String cacheClassName = rsvc
160: .getString(RuntimeConstants.RESOURCE_MANAGER_CACHE_CLASS);
161:
162: Object cacheObject = null;
163:
164: if (org.apache.commons.lang.StringUtils
165: .isNotEmpty(cacheClassName)) {
166:
167: try {
168: cacheObject = ClassUtils.getNewInstance(cacheClassName);
169: } catch (ClassNotFoundException cnfe) {
170: log
171: .error("The specified class for ResourceCache ("
172: + cacheClassName
173: + ") does not exist or is not accessible to the current classloader.");
174: cacheObject = null;
175: }
176:
177: if (!(cacheObject instanceof ResourceCache)) {
178: log
179: .error("The specified class for ResourceCache ("
180: + cacheClassName
181: + ") does not implement "
182: + 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: cacheObject = new ResourceCacheImpl();
193: }
194:
195: globalCache = (ResourceCache) cacheObject;
196:
197: globalCache.initialize(rsvc);
198:
199: log.trace("Default ResourceManager initialization complete.");
200: }
201:
202: /**
203: * This will produce a List of Hashtables, each hashtable contains the intialization info for a particular resource loader. This
204: * Hashtable will be passed in when initializing the the template loader.
205: */
206: private void assembleResourceLoaderInitializers() {
207: Vector resourceLoaderNames = rsvc.getConfiguration().getVector(
208: RuntimeConstants.RESOURCE_LOADER);
209: StringUtils.trimStrings(resourceLoaderNames);
210:
211: for (Iterator it = resourceLoaderNames.iterator(); it.hasNext();) {
212:
213: /*
214: * The loader id might look something like the following:
215: *
216: * file.resource.loader
217: *
218: * The loader id is the prefix used for all properties
219: * pertaining to a particular loader.
220: */
221: String loaderName = (String) it.next();
222: StringBuffer loaderID = new StringBuffer(loaderName);
223: loaderID.append(".").append(
224: RuntimeConstants.RESOURCE_LOADER);
225:
226: ExtendedProperties loaderConfiguration = rsvc
227: .getConfiguration().subset(loaderID.toString());
228:
229: /*
230: * we can't really count on ExtendedProperties to give us an empty set
231: */
232:
233: if (loaderConfiguration == null) {
234: log
235: .warn("ResourceManager : No configuration information for resource loader named '"
236: + loaderName + "'. Skipping.");
237:
238: continue;
239: }
240:
241: /*
242: * add the loader name token to the initializer if we need it
243: * for reference later. We can't count on the user to fill
244: * in the 'name' field
245: */
246:
247: loaderConfiguration.setProperty(RESOURCE_LOADER_IDENTIFIER,
248: 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,
273: final int resourceType, final String encoding)
274: throws ResourceNotFoundException, ParseErrorException,
275: Exception {
276: /*
277: * Check to see if the resource was placed in the cache.
278: * If it was placed in the cache then we will use
279: * the cached version of the resource. If not we
280: * will load it.
281: *
282: * Note: the type is included in the key to differentiate ContentResource
283: * (static content from #include) with a Template.
284: */
285:
286: String resourceKey = resourceType + resourceName;
287: Resource resource = globalCache.get(resourceKey);
288:
289: if (resource != null) {
290: /*
291: * refresh the resource
292: */
293:
294: try {
295: refreshResource(resource, encoding);
296: } catch (ResourceNotFoundException rnfe) {
297: /*
298: * something exceptional happened to that resource
299: * this could be on purpose,
300: * so clear the cache and try again
301: */
302:
303: globalCache.remove(resourceKey);
304:
305: return getResource(resourceName, resourceType, encoding);
306: } catch (ParseErrorException pee) {
307: log.error("ResourceManager.getResource() exception",
308: pee);
309: throw pee;
310: } catch (RuntimeException re) {
311: throw re;
312: } catch (Exception e) {
313: log.error("ResourceManager.getResource() exception", e);
314: throw e;
315: }
316: } else {
317: try {
318: /*
319: * it's not in the cache, so load it.
320: */
321:
322: resource = loadResource(resourceName, resourceType,
323: encoding);
324:
325: if (resource.getResourceLoader().isCachingOn()) {
326: globalCache.put(resourceKey, resource);
327: }
328: } catch (ResourceNotFoundException rnfe) {
329: log.error("ResourceManager : unable to find resource '"
330: + resourceName + "' in any resource loader.");
331: throw rnfe;
332: } catch (ParseErrorException pee) {
333: log
334: .error(
335: "ResourceManager.getResource() parse exception",
336: pee);
337: throw pee;
338: } catch (RuntimeException re) {
339: throw re;
340: } catch (Exception e) {
341: log.error(
342: "ResourceManager.getResource() exception new",
343: e);
344: throw e;
345: }
346: }
347:
348: return resource;
349: }
350:
351: /**
352: * Loads a resource from the current set of resource loaders.
353: *
354: * @param resourceName The name of the resource to retrieve.
355: * @param resourceType The type of resource (<code>RESOURCE_TEMPLATE</code>, <code>RESOURCE_CONTENT</code>, etc.).
356: * @param encoding The character encoding to use.
357: *
358: * @return Resource with the template parsed and ready.
359: *
360: * @throws ResourceNotFoundException if template not found from any available source.
361: * @throws ParseErrorException if template cannot be parsed due to syntax (or other) error.
362: * @throws Exception if a problem in parse
363: */
364: protected Resource loadResource(String resourceName,
365: int resourceType, String encoding)
366: throws ResourceNotFoundException, ParseErrorException,
367: Exception {
368: Resource resource = ResourceFactory.getResource(resourceName,
369: resourceType);
370:
371: resource.setRuntimeServices(rsvc);
372:
373: resource.setName(resourceName);
374: resource.setEncoding(encoding);
375:
376: /*
377: * Now we have to try to find the appropriate
378: * loader for this resource. We have to cycle through
379: * the list of available resource loaders and see
380: * which one gives us a stream that we can use to
381: * make a resource with.
382: */
383:
384: long howOldItWas = 0;
385:
386: for (Iterator it = resourceLoaders.iterator(); it.hasNext();) {
387: ResourceLoader resourceLoader = (ResourceLoader) it.next();
388: resource.setResourceLoader(resourceLoader);
389:
390: /*
391: * catch the ResourceNotFound exception
392: * as that is ok in our new multi-loader environment
393: */
394:
395: try {
396:
397: if (resource.process()) {
398: /*
399: * FIXME (gmj)
400: * moved in here - technically still
401: * a problem - but the resource needs to be
402: * processed before the loader can figure
403: * it out due to to the new
404: * multi-path support - will revisit and fix
405: */
406:
407: if (logWhenFound && log.isDebugEnabled()) {
408: log.debug("ResourceManager : found "
409: + resourceName + " with loader "
410: + resourceLoader.getClassName());
411: }
412:
413: howOldItWas = resourceLoader
414: .getLastModified(resource);
415:
416: break;
417: }
418: } catch (ResourceNotFoundException rnfe) {
419: /*
420: * that's ok - it's possible to fail in
421: * multi-loader environment
422: */
423: }
424: }
425:
426: /*
427: * Return null if we can't find a resource.
428: */
429: if (resource.getData() == null) {
430: throw new ResourceNotFoundException(
431: "Unable to find resource '" + resourceName + "'");
432: }
433:
434: /*
435: * some final cleanup
436: */
437:
438: resource.setLastModified(howOldItWas);
439: resource.setModificationCheckInterval(resource
440: .getResourceLoader().getModificationCheckInterval());
441:
442: resource.touch();
443:
444: return resource;
445: }
446:
447: /**
448: * Takes an existing resource, and 'refreshes' it. This generally means that the source of the resource is checked for changes
449: * according to some cache/check algorithm and if the resource changed, then the resource data is reloaded and re-parsed.
450: *
451: * @param resource resource to refresh
452: * @param encoding character encoding of the resource to refresh.
453: *
454: * @throws ResourceNotFoundException if template not found from current source for this Resource
455: * @throws ParseErrorException if template cannot be parsed due to syntax (or other) error.
456: * @throws Exception if a problem in parse
457: */
458: protected void refreshResource(final Resource resource,
459: final String encoding) throws ResourceNotFoundException,
460: ParseErrorException, Exception {
461:
462: /*
463: * The resource knows whether it needs to be checked
464: * or not, and the resource's loader can check to
465: * see if the source has been modified. If both
466: * these conditions are true then we must reload
467: * the input stream and parse it to make a new
468: * AST for the resource.
469: */
470: if (resource.requiresChecking()) {
471: /*
472: * touch() the resource to reset the counters
473: */
474:
475: resource.touch();
476:
477: if (resource.isSourceModified()) {
478: /*
479: * now check encoding info. It's possible that the newly declared
480: * encoding is different than the encoding already in the resource
481: * this strikes me as bad...
482: */
483:
484: if (!org.apache.commons.lang.StringUtils.equals(
485: resource.getEncoding(), encoding)) {
486: log.warn("Declared encoding for template '"
487: + resource.getName()
488: + "' is different on reload. Old = '"
489: + resource.getEncoding() + "' New = '"
490: + encoding);
491:
492: resource.setEncoding(encoding);
493: }
494:
495: /*
496: * read how old the resource is _before_
497: * processing (=>reading) it
498: */
499: long howOldItWas = resource.getResourceLoader()
500: .getLastModified(resource);
501:
502: /*
503: * read in the fresh stream and parse
504: */
505:
506: resource.process();
507:
508: /*
509: * now set the modification info and reset
510: * the modification check counters
511: */
512:
513: resource.setLastModified(howOldItWas);
514: }
515: }
516: }
517:
518: /**
519: * Gets the named resource. Returned class type corresponds to specified type (i.e. <code>Template</code> to <code>
520: * RESOURCE_TEMPLATE</code>).
521: *
522: * @param resourceName The name of the resource to retrieve.
523: * @param resourceType The type of resource (<code>RESOURCE_TEMPLATE</code>, <code>RESOURCE_CONTENT</code>, etc.).
524: *
525: * @return Resource with the template parsed and ready.
526: *
527: * @throws ResourceNotFoundException if template not found from any available source.
528: * @throws ParseErrorException if template cannot be parsed due to syntax (or other) error.
529: * @throws Exception if a problem in parse
530: *
531: * @deprecated Use {@link #getResource(String resourceName, int resourceType, String encoding )}
532: */
533: public Resource getResource(String resourceName, int resourceType)
534: throws ResourceNotFoundException, ParseErrorException,
535: Exception {
536: return getResource(resourceName, resourceType,
537: RuntimeConstants.ENCODING_DEFAULT);
538: }
539:
540: /**
541: * Determines if a template exists, and returns name of the loader that provides it. This is a slightly less hokey way to
542: * support the Velocity.templateExists() utility method, which was broken when per-template encoding was introduced. We can
543: * revisit this.
544: *
545: * @param resourceName Name of template or content resource
546: *
547: * @return class name of loader than can provide it
548: */
549: public String getLoaderNameForResource(String resourceName) {
550: /*
551: * loop through our loaders...
552: */
553: for (Iterator it = resourceLoaders.iterator(); it.hasNext();) {
554: ResourceLoader resourceLoader = (ResourceLoader) it.next();
555:
556: InputStream is = null;
557:
558: /*
559: * if we find one that can provide the resource,
560: * return the name of the loaders's Class
561: */
562: try {
563: is = resourceLoader.getResourceStream(resourceName);
564:
565: if (is != null) {
566: return resourceLoader.getClass().toString();
567: }
568: } catch (ResourceNotFoundException rnfe) {
569: /*
570: * this isn't a problem. keep going
571: */
572: } finally {
573:
574: /*
575: * if we did find one, clean up because we were
576: * returned an open stream
577: */
578: if (is != null) {
579:
580: try {
581: is.close();
582: } catch (IOException supressed) {
583: }
584: }
585: }
586: }
587:
588: return null;
589: }
590: }
|