001 /**
002 * ================================================
003 * LibLoader : a free Java resource loading library
004 * ================================================
005 *
006 * Project Info: http://reporting.pentaho.org/libloader/
007 *
008 * (C) Copyright 2006, by Pentaho Corporation and Contributors.
009 *
010 * This library is free software; you can redistribute it and/or modify it under the terms
011 * of the GNU Lesser General Public License as published by the Free Software Foundation;
012 * either version 2.1 of the License, or (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
015 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016 * See the GNU Lesser General Public License for more details.
017 *
018 * You should have received a copy of the GNU Lesser General Public License along with this
019 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
020 * Boston, MA 02111-1307, USA.
021 *
022 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
023 * in the United States and other countries.]
024 *
025 *
026 * ------------
027 * $Id: ResourceManager.java 2746 2007-04-04 11:12:36Z taqua $
028 * ------------
029 * (C) Copyright 2006, by Pentaho Corporation.
030 */
031 package org.jfree.resourceloader;
032
033 import java.net.URL;
034 import java.util.ArrayList;
035 import java.util.HashSet;
036 import java.util.Iterator;
037 import java.util.Map;
038 import java.util.Set;
039
040 import org.jfree.resourceloader.cache.NullResourceDataCache;
041 import org.jfree.resourceloader.cache.NullResourceFactoryCache;
042 import org.jfree.resourceloader.cache.ResourceDataCache;
043 import org.jfree.resourceloader.cache.ResourceDataCacheEntry;
044 import org.jfree.resourceloader.cache.ResourceDataCacheProvider;
045 import org.jfree.resourceloader.cache.ResourceFactoryCache;
046 import org.jfree.resourceloader.cache.ResourceFactoryCacheProvider;
047 import org.jfree.util.Configuration;
048 import org.jfree.util.Log;
049 import org.jfree.util.ObjectUtilities;
050
051 /**
052 * The resource manager takes care about the loaded resources, performs caching, if needed and is the central instance
053 * when dealing with resources. Resource loading is a two-step process. In the first step, the {@link ResourceLoader}
054 * accesses the physical storage or network connection to read in the binary data. The loaded {@link ResourceData}
055 * carries versioning information with it an can be cached indendently from the produced result. Once the loading is
056 * complete, a {@link ResourceFactory} interprets the binary data and produces a Java-Object from it.
057 * <p/>
058 * Resources are identified by an Resource-Key and some optional loader parameters (which can be used to parametrize the
059 * resource-factories).
060 *
061 * @author Thomas Morgner
062 * @see ResourceData
063 * @see ResourceLoader
064 * @see ResourceFactory
065 */
066 public class ResourceManager
067 {
068 /**
069 * A set that contains the class-names of all cache-modules, which could not be instantiated correctly.
070 * This set is used to limit the number of warnings in the log to exactly one per class.
071 */
072 private static final Set failedModules = new HashSet();
073
074 private ArrayList resourceLoaders;
075 private ArrayList resourceFactories;
076 private ResourceDataCache dataCache;
077 private ResourceFactoryCache factoryCache;
078
079 private static final String LOADER_PREFIX = "org.jfree.resourceloader.loader.";
080 private static final String FACTORY_TYPE_PREFIX = "org.jfree.resourceloader.factory.type.";
081 public static final String DATA_CACHE_PROVIDER_KEY = "org.jfree.resourceloader.cache.DataCacheProvider";
082 public static final String FACTORY_CACHE_PROVIDER_KEY = "org.jfree.resourceloader.cache.FactoryCacheProvider";
083
084 public ResourceManager()
085 {
086 resourceLoaders = new ArrayList();
087 resourceFactories = new ArrayList();
088 dataCache = new NullResourceDataCache();
089 factoryCache = new NullResourceFactoryCache();
090 }
091
092 /**
093 * Creates a ResourceKey that carries no Loader-Parameters from the given object.
094 *
095 * @param data the key-data
096 * @return the generated resource-key, never null.
097 * @throws ResourceKeyCreationException if the key-creation failed.
098 */
099 public synchronized ResourceKey createKey(final Object data)
100 throws ResourceKeyCreationException
101 {
102 return createKey(data, null);
103 }
104
105 /**
106 * Creates a ResourceKey that carries the given Loader-Parameters contained in the optional map.
107 *
108 * @param data the key-data
109 * @param parameters an optional map of parameters.
110 * @return the generated resource-key, never null.
111 * @throws ResourceKeyCreationException if the key-creation failed.
112 */
113 public synchronized ResourceKey createKey(final Object data, final Map parameters)
114 throws ResourceKeyCreationException
115 {
116 if (data == null)
117 {
118 throw new NullPointerException("Key data must not be null.");
119 }
120
121 final Iterator values = resourceLoaders.iterator();
122 while (values.hasNext())
123 {
124 final ResourceLoader loader = (ResourceLoader) values.next();
125 try
126 {
127 final ResourceKey key = loader.createKey(data, parameters);
128 if (key != null)
129 {
130 return key;
131 }
132 }
133 catch (ResourceKeyCreationException rkce)
134 {
135 // ignore it.
136 }
137 }
138
139 throw new ResourceKeyCreationException
140 ("Unable to create key: No loader was able " +
141 "to handle the given key data: " + data);
142 }
143
144 /**
145 * Derives a new key from the given resource-key. Only keys for a hierarchical storage system (like file-systems or
146 * URLs) can have derived keys. Since LibLoader 0.3.0 only hierarchical keys can be derived. For that, the deriving
147 * path must be given as String.
148 * <p/>
149 * Before trying to derive the key, the system tries to interpret the path as absolute key-value.
150 *
151 * @param parent the parent key, must never be null
152 * @param path the relative path, that is used to derive the key.
153 * @return the derived key.
154 */
155 public ResourceKey deriveKey(final ResourceKey parent, final String path)
156 throws ResourceKeyCreationException
157 {
158 return deriveKey(parent, path, null);
159 }
160
161 /**
162 * Derives a new key from the given resource-key. Only keys for a hierarchical storage system (like file-systems or
163 * URLs) can have derived keys. Since LibLoader 0.3.0 only hierarchical keys can be derived. For that, the deriving
164 * path must be given as String.
165 * <p/>
166 * The optional parameter-map will be applied to the derived key after the parent's parameters have been copied to
167 * the new key.
168 * <p/>
169 * Before trying to derive the key, the system tries to interpret the path as absolute key-value.
170 *
171 * @param parent the parent key, or null to interpret the path as absolute key.
172 * @param path the relative path, that is used to derive the key.
173 * @return the derived key.
174 */
175 public ResourceKey deriveKey(final ResourceKey parent, final String path, final Map parameters)
176 throws ResourceKeyCreationException
177 {
178 if (path == null)
179 {
180 throw new NullPointerException("Key data must not be null.");
181 }
182 if (parent == null)
183 {
184 return createKey(path, parameters);
185 }
186
187 // First, try to derive the resource directly. This makes sure, that we preserve the parent's context.
188 // If a file is derived, we assume that the result will be a file; and only if that fails we'll try to
189 // query the other contexts. If the parent is an URL-context, the result is assumed to be an URL as well.
190 ResourceKeyCreationException rce = null;
191 for (int i = 0; i < resourceLoaders.size(); i++)
192 {
193 final ResourceLoader loader = (ResourceLoader) resourceLoaders.get(i);
194 if (loader.isSupportedKey(parent) == false)
195 {
196 continue;
197 }
198 try
199 {
200 final ResourceKey key = loader.deriveKey(parent, path, parameters);
201 if (key != null)
202 {
203 return key;
204 }
205 }
206 catch (ResourceKeyCreationException rcke)
207 {
208 rce = rcke;
209 }
210 }
211
212 // First, try to load the key as absolute value.
213 // This assumes, that we have no catch-all implementation.
214 for (int i = 0; i < resourceLoaders.size(); i++)
215 {
216 final ResourceLoader loader = (ResourceLoader) resourceLoaders.get(i);
217 final ResourceKey key = loader.createKey(path, parameters);
218 if (key != null)
219 {
220 return key;
221 }
222 }
223
224 if (rce != null)
225 {
226 throw rce;
227 }
228 throw new ResourceKeyCreationException
229 ("Unable to create key: No such schema or the key was not recognized.");
230 }
231
232 /**
233 * Tries to find the first resource-loader that would be able to process the key.
234 *
235 * @param key the resource-key.
236 * @return the resourceloader for that key, or null, if no resource-loader is able to process the key.
237 */
238 private ResourceLoader findBySchema(final ResourceKey key)
239 {
240 for (int i = 0; i < resourceLoaders.size(); i++)
241 {
242 final ResourceLoader loader = (ResourceLoader) resourceLoaders.get(i);
243 if (loader.isSupportedKey(key))
244 {
245 return loader;
246 }
247 }
248 return null;
249 }
250
251 /**
252 * Tries to convert the resource-key into an URL. Not all resource-keys have an URL representation. This method
253 * exists to make it easier to connect LibLoader to other resource-loading frameworks.
254 *
255 * @param key the resource-key
256 * @return the URL for the key, or null if there is no such key.
257 */
258 public URL toURL(final ResourceKey key)
259 {
260 final ResourceLoader loader = findBySchema(key);
261 if (loader == null)
262 {
263 return null;
264 }
265 return loader.toURL(key);
266 }
267
268 public ResourceData load(final ResourceKey key) throws ResourceLoadingException
269 {
270 final ResourceLoader loader = findBySchema(key);
271 if (loader == null)
272 {
273 throw new ResourceLoadingException
274 ("Invalid key: No resource-loader registered for schema: " + key.getSchema());
275 }
276
277 final ResourceDataCacheEntry cached = dataCache.get(key);
278 if (cached != null)
279 {
280 final ResourceData data = cached.getData();
281 // check, whether it is valid.
282 if (cached.getStoredVersion() < 0)
283 {
284 // a non versioned entry is always valid. (Maybe this is from a Jar-URL?)
285 return data;
286 }
287
288 final long version = data.getVersion(this);
289 if (version < 0)
290 {
291 // the system is no longer able to retrieve the version information?
292 // (but versioning information must have been available in the past)
293 // oh, that's bad. Assume the worst and re-read the data.
294 dataCache.remove(data);
295 }
296 else if (cached.getStoredVersion() == version)
297 {
298 return data;
299 }
300 else
301 {
302 dataCache.remove(data);
303 }
304 }
305 final ResourceData data = loader.load(key);
306 return dataCache.put(this, data);
307 }
308
309 public Resource createDirectly(final Object keyValue, final Class target)
310 throws ResourceLoadingException,
311 ResourceCreationException,
312 ResourceKeyCreationException
313 {
314 final ResourceKey key = createKey(keyValue);
315 return create(key, null, target);
316 }
317
318 public Resource create(final ResourceKey key, final ResourceKey context, final Class target)
319 throws ResourceLoadingException, ResourceCreationException
320 {
321 if (target == null)
322 {
323 throw new NullPointerException("Target must not be null");
324 }
325 if (key == null)
326 {
327 throw new NullPointerException("Key must not be null.");
328 }
329 return create(key, context, new Class[]{target});
330 }
331
332 public Resource create(final ResourceKey key, final ResourceKey context)
333 throws ResourceLoadingException, ResourceCreationException
334 {
335 return create(key, context, (Class[]) null);
336 }
337
338 public Resource create(final ResourceKey key, final ResourceKey context, final Class[] target)
339 throws ResourceLoadingException, ResourceCreationException
340 {
341 if (key == null)
342 {
343 throw new NullPointerException("Key must not be null.");
344 }
345
346 // ok, we have a handle to the data, and the data is current.
347 // Lets check whether we also have a cached result.
348 final Resource resource = factoryCache.get(key);
349 if (resource != null)
350 {
351 if (isResourceUnchanged(resource))
352 {
353 // mama, look i am a good cache manager ...
354 return resource;
355 }
356 else
357 {
358 // someone evil changed one of the dependent resources ...
359 factoryCache.remove(resource);
360 }
361 }
362
363 // AutoMode ..
364 if (target == null)
365 {
366 return autoCreateResource(key, context);
367 }
368
369 ResourceCreationException exception = null;
370 final ResourceData data = load(key);
371 for (int i = 0; i < resourceFactories.size(); i++)
372 {
373 final ResourceFactory fact =
374 (ResourceFactory) resourceFactories.get(i);
375 if (isSupportedTarget(target, fact) == false)
376 {
377 // Unsupported keys: Try the next factory ..
378 continue;
379 }
380
381 try
382 {
383 return performCreate(data, fact, context);
384 }
385 catch (ContentNotRecognizedException ce)
386 {
387 // Ignore it, unless it is the last one.
388 }
389 catch (ResourceCreationException rex)
390 {
391 // ignore it, try the next factory ...
392 exception = rex;
393 if (Log.isDebugEnabled())
394 {
395 Log.debug("Failed at " + fact.getClass() + ": ", rex);
396 }
397 }
398
399 }
400
401 if (exception != null)
402 {
403 throw exception;
404 }
405 throw new ContentNotRecognizedException
406 ("None of the selected factories was able to handle the given data: " + key);
407 }
408
409 private boolean isSupportedTarget(final Class[] target, final ResourceFactory fact)
410 {
411 final Class factoryType = fact.getFactoryType();
412 for (int j = 0; j < target.length; j++)
413 {
414 final Class aClass = target[j];
415 if (aClass != null && aClass.isAssignableFrom(factoryType))
416 {
417 return true;
418 }
419 }
420 return false;
421 }
422
423 private Resource autoCreateResource(final ResourceKey key,
424 final ResourceKey context)
425 throws ResourceLoadingException, ResourceCreationException
426 {
427 final ResourceData data = load(key);
428
429 final Iterator it = resourceFactories.iterator();
430 while (it.hasNext())
431 {
432 final ResourceFactory fact = (ResourceFactory) it.next();
433 try
434 {
435 final Resource res = performCreate(data, fact, context);
436 if (res != null)
437 {
438 return res;
439 }
440 }
441 catch (ResourceCreationException rex)
442 {
443 // ignore it, try the next factory ...
444 }
445 }
446 throw new ResourceCreationException
447 ("No known factory was able to handle the given data.");
448 }
449
450 private Resource performCreate(final ResourceData data,
451 final ResourceFactory fact,
452 final ResourceKey context)
453 throws ResourceLoadingException, ResourceCreationException
454 {
455 final Resource created = fact.create(this, data, context);
456 factoryCache.put(created);
457 return created;
458 }
459
460 private boolean isResourceUnchanged(final Resource resource)
461 throws ResourceLoadingException
462 {
463 final ResourceKey[] deps = resource.getDependencies();
464 for (int i = 0; i < deps.length; i++)
465 {
466 final ResourceKey dep = deps[i];
467 final long version = resource.getVersion(dep);
468 if (version == -1)
469 {
470 // non-versioning key, ignore it.
471 continue;
472 }
473
474 final ResourceData data = load(dep);
475 if (data.getVersion(this) != version)
476 {
477 // oh, my bad, an outdated or changed entry.
478 // We have to re-read the whole thing.
479 return false;
480 }
481 }
482 // all versions have been confirmed to be valid. Nice, we can use the
483 // cached product.
484 return true;
485 }
486
487 public ResourceDataCache getDataCache()
488 {
489 return dataCache;
490 }
491
492 public void setDataCache(final ResourceDataCache dataCache)
493 {
494 if (dataCache == null)
495 {
496 throw new NullPointerException();
497 }
498 this.dataCache = dataCache;
499 }
500
501 public ResourceFactoryCache getFactoryCache()
502 {
503 return factoryCache;
504 }
505
506 public void setFactoryCache(final ResourceFactoryCache factoryCache)
507 {
508 if (factoryCache == null)
509 {
510 throw new NullPointerException();
511 }
512 this.factoryCache = factoryCache;
513 }
514
515 public void registerDefaults()
516 {
517 // Create all known resource loaders ...
518 registerDefaultLoaders();
519
520 // Register all known factories ...
521 registerDefaultFactories();
522
523 // add the caches ..
524 registerDataCache();
525 registerFactoryCache();
526 }
527
528 public void registerDefaultFactories()
529 {
530 final Configuration config = LibLoaderBoot.getInstance().getGlobalConfig();
531 final Iterator itType = config.findPropertyKeys(FACTORY_TYPE_PREFIX);
532 while (itType.hasNext())
533 {
534 final String key = (String) itType.next();
535 final String factoryClass = config.getConfigProperty(key);
536
537 final Object maybeFactory = ObjectUtilities.loadAndInstantiate
538 (factoryClass, ResourceManager.class, ResourceFactory.class);
539 if (maybeFactory instanceof ResourceFactory == false)
540 {
541 continue;
542 }
543
544 final ResourceFactory factory = (ResourceFactory) maybeFactory;
545 factory.initializeDefaults();
546 registerFactory(factory);
547 }
548 }
549
550 public void registerDataCache()
551 {
552 final Configuration config = LibLoaderBoot.getInstance().getGlobalConfig();
553 final String dataCacheProviderClass =
554 config.getConfigProperty(DATA_CACHE_PROVIDER_KEY);
555 if (dataCacheProviderClass == null)
556 {
557 return;
558 }
559 final Object maybeDataCacheProvider =
560 ObjectUtilities.loadAndInstantiate
561 (dataCacheProviderClass, ResourceManager.class, ResourceDataCacheProvider.class);
562 if (maybeDataCacheProvider instanceof ResourceDataCacheProvider)
563 {
564 final ResourceDataCacheProvider provider = (ResourceDataCacheProvider) maybeDataCacheProvider;
565 try
566 {
567 final ResourceDataCache cache = provider.createDataCache();
568 if (cache != null)
569 {
570 setDataCache(cache);
571 }
572 }
573 catch (Throwable e)
574 {
575 // ok, did not work ...
576 synchronized (failedModules)
577 {
578 if (failedModules.contains(dataCacheProviderClass) == false)
579 {
580 Log.warn("Failed to create data cache: " + e.getLocalizedMessage());
581 failedModules.add(dataCacheProviderClass);
582 }
583 }
584 }
585 }
586 }
587
588 public void registerFactoryCache()
589 {
590 final Configuration config = LibLoaderBoot.getInstance().getGlobalConfig();
591 final String cacheProviderClass = config.getConfigProperty
592 (FACTORY_CACHE_PROVIDER_KEY);
593 if (cacheProviderClass == null)
594 {
595 return;
596 }
597 final Object maybeCacheProvider = ObjectUtilities.loadAndInstantiate
598 (cacheProviderClass, ResourceManager.class, ResourceFactoryCacheProvider.class);
599
600 if (maybeCacheProvider != null)
601 {
602 final ResourceFactoryCacheProvider provider = (ResourceFactoryCacheProvider) maybeCacheProvider;
603 try
604 {
605 final ResourceFactoryCache cache = provider.createFactoryCache();
606 if (cache != null)
607 {
608 setFactoryCache(cache);
609 }
610 }
611 catch (Throwable e)
612 {
613 synchronized (failedModules)
614 {
615 if (failedModules.contains(cacheProviderClass) == false)
616 {
617 Log.warn("Failed to create factory cache: " + e.getLocalizedMessage());
618 failedModules.add(cacheProviderClass);
619 }
620 }
621 }
622 }
623 }
624
625 public void registerDefaultLoaders()
626 {
627 final Configuration config = LibLoaderBoot.getInstance().getGlobalConfig();
628 final Iterator it = config.findPropertyKeys(LOADER_PREFIX);
629 while (it.hasNext())
630 {
631 final String key = (String) it.next();
632 final String value = config.getConfigProperty(key);
633 final Object o = ObjectUtilities.loadAndInstantiate(value, ResourceManager.class, ResourceLoader.class);
634 if (o != null)
635 {
636 final ResourceLoader loader = (ResourceLoader) o;
637 //Log.debug("Registering loader for " + loader.getSchema());
638 registerLoader(loader);
639 }
640 }
641 }
642
643 public void registerLoader(final ResourceLoader loader)
644 {
645 if (loader == null)
646 {
647 throw new NullPointerException("ResourceLoader must not be null.");
648 }
649 loader.setResourceManager(this);
650 resourceLoaders.add(loader);
651 }
652
653 public void registerFactory(final ResourceFactory factory)
654 {
655 if (factory == null)
656 {
657 throw new NullPointerException("ResourceFactory must not be null.");
658 }
659 resourceFactories.add(factory);
660 }
661
662 }