Source for org.jfree.resourceloader.ResourceManager

   1: /**
   2:  * ================================================
   3:  * LibLoader : a free Java resource loading library
   4:  * ================================================
   5:  *
   6:  * Project Info:  http://reporting.pentaho.org/libloader/
   7:  *
   8:  * (C) Copyright 2006, by Pentaho Corporation and Contributors.
   9:  *
  10:  * This library is free software; you can redistribute it and/or modify it under the terms
  11:  * of the GNU Lesser General Public License as published by the Free Software Foundation;
  12:  * either version 2.1 of the License, or (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  15:  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  16:  * See the GNU Lesser General Public License for more details.
  17:  *
  18:  * You should have received a copy of the GNU Lesser General Public License along with this
  19:  * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  20:  * Boston, MA 02111-1307, USA.
  21:  *
  22:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  23:  * in the United States and other countries.]
  24:  *
  25:  *
  26:  * ------------
  27:  * $Id: ResourceManager.java 2746 2007-04-04 11:12:36Z taqua $
  28:  * ------------
  29:  * (C) Copyright 2006, by Pentaho Corporation.
  30:  */
  31: package org.jfree.resourceloader;
  32: 
  33: import java.net.URL;
  34: import java.util.ArrayList;
  35: import java.util.HashSet;
  36: import java.util.Iterator;
  37: import java.util.Map;
  38: import java.util.Set;
  39: 
  40: import org.jfree.resourceloader.cache.NullResourceDataCache;
  41: import org.jfree.resourceloader.cache.NullResourceFactoryCache;
  42: import org.jfree.resourceloader.cache.ResourceDataCache;
  43: import org.jfree.resourceloader.cache.ResourceDataCacheEntry;
  44: import org.jfree.resourceloader.cache.ResourceDataCacheProvider;
  45: import org.jfree.resourceloader.cache.ResourceFactoryCache;
  46: import org.jfree.resourceloader.cache.ResourceFactoryCacheProvider;
  47: import org.jfree.util.Configuration;
  48: import org.jfree.util.Log;
  49: import org.jfree.util.ObjectUtilities;
  50: 
  51: /**
  52:  * The resource manager takes care about the loaded resources, performs caching, if needed and is the central instance
  53:  * when dealing with resources. Resource loading is a two-step process. In the first step, the {@link ResourceLoader}
  54:  * accesses the physical storage or network connection to read in the binary data. The loaded {@link ResourceData}
  55:  * carries versioning information with it an can be cached indendently from the produced result. Once the loading is
  56:  * complete, a {@link ResourceFactory} interprets the binary data and produces a Java-Object from it.
  57:  * <p/>
  58:  * Resources are identified by an Resource-Key and some optional loader parameters (which can be used to parametrize the
  59:  * resource-factories).
  60:  *
  61:  * @author Thomas Morgner
  62:  * @see ResourceData
  63:  * @see ResourceLoader
  64:  * @see ResourceFactory
  65:  */
  66: public class ResourceManager
  67: {
  68:   /**
  69:    * A set that contains the class-names of all cache-modules, which could not be instantiated correctly.
  70:    * This set is used to limit the number of warnings in the log to exactly one per class.
  71:    */
  72:   private static final Set failedModules = new HashSet();
  73: 
  74:   private ArrayList resourceLoaders;
  75:   private ArrayList resourceFactories;
  76:   private ResourceDataCache dataCache;
  77:   private ResourceFactoryCache factoryCache;
  78: 
  79:   private static final String LOADER_PREFIX = "org.jfree.resourceloader.loader.";
  80:   private static final String FACTORY_TYPE_PREFIX = "org.jfree.resourceloader.factory.type.";
  81:   public static final String DATA_CACHE_PROVIDER_KEY = "org.jfree.resourceloader.cache.DataCacheProvider";
  82:   public static final String FACTORY_CACHE_PROVIDER_KEY = "org.jfree.resourceloader.cache.FactoryCacheProvider";
  83: 
  84:   public ResourceManager()
  85:   {
  86:     resourceLoaders = new ArrayList();
  87:     resourceFactories = new ArrayList();
  88:     dataCache = new NullResourceDataCache();
  89:     factoryCache = new NullResourceFactoryCache();
  90:   }
  91: 
  92:   /**
  93:    * Creates a ResourceKey that carries no Loader-Parameters from the given object.
  94:    *
  95:    * @param data the key-data
  96:    * @return the generated resource-key, never null.
  97:    * @throws ResourceKeyCreationException if the key-creation failed.
  98:    */
  99:   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: }