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