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 }