/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.wicket.request.resource; import java.util.Locale; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import org.apache.wicket.request.resource.ResourceReference.Key; import org.apache.wicket.util.lang.Args; import org.apache.wicket.util.lang.Generics; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Allows to register and lookup {@link ResourceReference}s per Application. * * @see org.apache.wicket.Application#getResourceReferenceRegistry() * @see org.apache.wicket.Application#newResourceReferenceRegistry() * * @author Matej Knopp * @author Juergen Donnerstag */ public class ResourceReferenceRegistry { /** Log for reporting. */ private static final Logger log = LoggerFactory.getLogger(ResourceReferenceRegistry.class); // Scan classes and its superclasses for static ResourceReference fields. For each // RR field found, the callback method is called and the RR gets registered. It's kind of // auto-register all RRs in your Component hierarchy. private ClassScanner scanner = new ClassScanner() { @Override boolean foundResourceReference(final ResourceReference reference) { // register the RR found (static field of Scope class) return registerResourceReference(reference); } }; // The Map (registry) maintaining the resource references private final ConcurrentHashMap<Key, ResourceReference> map = Generics.newConcurrentHashMap(); // If combinations of parameters (Key) have no registered resource reference yet, a default // resource reference can be created and added to the registry. The following list keeps track // of all auto added references. private Queue<Key> autoAddedQueue; // max entries. If the queue is full and new references are auto generated, references are // removed starting with the first entry and unregistered from the registry. private int autoAddedCapacity = 1000; /** * A simple implementation of {@link IResourceReferenceFactory} that creates * {@link PackageResourceReference} */ public static class DefaultResourceReferenceFactory implements IResourceReferenceFactory { @Override public ResourceReference create(Key key) { ResourceReference result = null; if (PackageResource.exists(key)) { result = new PackageResourceReference(key); } return result; } } /** * The factory to use when a ResourceReference is not previously * registered and a new instance should be create */ private IResourceReferenceFactory resourceReferenceFactory; /** * Constructor. * * <p>Uses DefaultResourceReferenceFactory to create ResourceReference when there is * no registered one for the requested attributes</p> */ public ResourceReferenceRegistry() { this(new DefaultResourceReferenceFactory()); } /** * Constructor * * @param resourceReferenceFactory * The factory that will create ResourceReference by Key when there is no registered one */ public ResourceReferenceRegistry(IResourceReferenceFactory resourceReferenceFactory) { this.resourceReferenceFactory = Args.notNull(resourceReferenceFactory, "resourceReferenceFactory"); // Initial the auto-add list for a maximum of 1000 entries setAutoAddedCapacity(autoAddedCapacity); } /** * Registers the given {@link ResourceReference}. * <p> * {@link ResourceReference#canBeRegistered()} must return <code>true</code>. Else, the resource * reference will not be registered. * * @param reference * the reference to register * @return {@code true} if the resource was registered successfully or has been registered previously * already. */ public final boolean registerResourceReference(final ResourceReference reference) { return null != _registerResourceReference(reference); } /** * Registers the given {@link ResourceReference}. * <p> * {@link ResourceReference#canBeRegistered()} must return <code>true</code>. Else, the resource * reference will not be registered. * * @param reference * the reference to register * @return {@code true} if the resource was registered successfully or has been registered previously * already. */ private Key _registerResourceReference(final ResourceReference reference) { Args.notNull(reference, "reference"); if (reference.canBeRegistered()) { Key key = reference.getKey(); map.putIfAbsent(key, reference); return key; } log.warn("{} cannot be added to the registry.", reference.getClass().getName()); return null; } /** * Unregisters a {@link ResourceReference} by its identifier. * * @param key * the {@link ResourceReference}'s identifier * @return The removed ResourceReference or {@code null} if the registry did not contain an entry for this key. */ public final ResourceReference unregisterResourceReference(final Key key) { Args.notNull(key, "key"); // remove from registry ResourceReference removed = map.remove(key); // remove from auto-added list, in case the RR was auto-added if (autoAddedQueue != null) { autoAddedQueue.remove(key); } return removed; } /** * Get a resource reference matching the parameters from the registry or if not found and * requested, create an default resource reference and add it to the registry. * <p> * Part of the search is scanning the class (scope) and it's superclass for static * ResourceReference fields. Found fields get registered automatically (but are different from * auto-generated ResourceReferences). * * @see #createDefaultResourceReference(org.apache.wicket.request.resource.ResourceReference.Key) * @see ClassScanner * * @param scope * The scope of resource reference (e.g. the Component's class) * @param name * The name of resource reference (e.g. filename) * @param locale * see Component * @param style * see Component * @param variation * see Component * @param strict * If true, "weaker" combination of scope, name, locale etc. are not tested * @param createIfNotFound * If true a default resource reference is created if no entry can be found in the * registry. The newly created resource reference will be added to the registry. * @return Either the resource reference found in the registry or, if requested, a resource * reference automatically created based on the parameters provided. The automatically * created resource reference will automatically be added to the registry. */ public final ResourceReference getResourceReference(final Class<?> scope, final String name, final Locale locale, final String style, final String variation, final boolean strict, final boolean createIfNotFound) { return getResourceReference(new Key(scope.getName(), name, locale, style, variation), strict, createIfNotFound); } /** * Get a resource reference matching the parameters from the registry or if not found and * requested, create an default resource reference and add it to the registry. * <p> * Part of the search is scanning the class (scope) and it's superclass for static * ResourceReference fields. Found fields get registered automatically (but are different from * auto-generated ResourceReferences). * * @see #createDefaultResourceReference(org.apache.wicket.request.resource.ResourceReference.Key) * @see ClassScanner * * @param key * The data making up the resource reference * @param strict * If true, "weaker" combination of scope, name, locale etc. are not tested * @param createIfNotFound * If true a default resource reference is created if no entry can be found in the * registry. The newly created resource reference will be added to the registry. * @return Either the resource reference found in the registry or, if requested, a resource * reference automatically created based on the parameters provided. The automatically * created resource reference will automatically be added to the registry. */ public final ResourceReference getResourceReference(final Key key, final boolean strict, final boolean createIfNotFound) { ResourceReference resource = _getResourceReference(key.getScope(), key.getName(), key.getLocale(), key.getStyle(), key.getVariation(), strict); // Nothing found so far? if (resource == null) { // Scan the class (scope) and it's super classes for static fields containing resource // references. Such resources are registered as normal resource reference (not // auto-added). if (scanner.scanClass(key.getScopeClass()) > 0) { // At least one new resource reference got registered => Search the registry again resource = _getResourceReference(key.getScope(), key.getName(), key.getLocale(), key.getStyle(), key.getVariation(), strict); } // Still nothing found => Shall a new reference be auto-created? if ((resource == null) && createIfNotFound) { resource = addDefaultResourceReference(key); } } return resource; } /** * Get a resource reference matching the parameters from the registry. * * @param scope * The scope of resource reference (e.g. the Component's class) * @param name * The name of resource reference (e.g. filename) * @param locale * see Component * @param style * see Component * @param variation * see Component * @param strict * If true, "weaker" combination of scope, name, locale etc. are not tested * @return Either the resource reference found in the registry or null if not found */ private ResourceReference _getResourceReference(final String scope, final String name, final Locale locale, final String style, final String variation, final boolean strict) { // Create a registry key containing all of the relevant attributes Key key = new Key(scope, name, locale, style, variation); // Get resource reference matching exactly the attrs provided ResourceReference res = map.get(key); if ((res != null) || strict) { return res; } res = _getResourceReference(scope, name, locale, style, null, true); if (res == null) { res = _getResourceReference(scope, name, locale, null, variation, true); } if (res == null) { res = _getResourceReference(scope, name, locale, null, null, true); } if (res == null) { res = _getResourceReference(scope, name, null, style, variation, true); } if (res == null) { res = _getResourceReference(scope, name, null, style, null, true); } if (res == null) { res = _getResourceReference(scope, name, null, null, variation, true); } if (res == null) { res = _getResourceReference(scope, name, null, null, null, true); } return res; } /** * Creates a default resource reference and registers it. * * @param key * the data making up the resource reference * @return The default resource created */ private ResourceReference addDefaultResourceReference(final Key key) { // Can be subclassed to create other than PackagedResourceReference ResourceReference reference = createDefaultResourceReference(key); if (reference != null) { // number of RRs which can be auto-added is restricted (cache size). Remove entries, and // unregister excessive ones, if needed. enforceAutoAddedCacheSize(getAutoAddedCapacity()); // Register the new RR _registerResourceReference(reference); // Add it to the auto-added list if (autoAddedQueue != null) { autoAddedQueue.add(key); } } else { log.warn("A ResourceReference wont be created for a resource with key [{}] because it cannot be located.", key); } return reference; } /** * The number of {@link ResourceReference}s which can be auto-added is restricted (cache size). Remove entries, and * unregister excessive ones, if needed. * * @param maxSize * Remove all entries, head first, until auto-added cache is smaller than maxSize. */ private void enforceAutoAddedCacheSize(int maxSize) { if (autoAddedQueue != null) { while (autoAddedQueue.size() > maxSize) { // remove entry from auto-added list Key first = autoAddedQueue.remove(); // remove entry from registry map.remove(first); } } } /** * Creates a default resource reference in case no registry entry and it was requested to create * one. * <p> * A {@link PackageResourceReference} will be created by default * * @param key * the data making up the resource reference * @return The {@link ResourceReference} created or {@code null} if not successful */ protected ResourceReference createDefaultResourceReference(final Key key) { IResourceReferenceFactory factory = getResourceReferenceFactory(); if (factory == null) { factory = new DefaultResourceReferenceFactory(); } return factory.create(key); } /** * Set the cache size in number of entries * * @param autoAddedCapacity * A value < 0 will disable aging of auto-create resource references. They will be * created, added to the registry and live their until manually removed or the * application shuts down. */ public final void setAutoAddedCapacity(final int autoAddedCapacity) { // Disable aging of auto-added references? if (autoAddedCapacity < 0) { // unregister all auto-added references clearAutoAddedEntries(); // disable aging from now on autoAddedQueue = null; } else { this.autoAddedCapacity = autoAddedCapacity; if (autoAddedQueue == null) { autoAddedQueue = new ConcurrentLinkedQueue<Key>(); } else { // remove all extra entries if necessary enforceAutoAddedCacheSize(autoAddedCapacity); } } } /** * Gets cache size in number of entries * * @return capacity */ public final int getAutoAddedCapacity() { return autoAddedCapacity; } /** * Unregisters all auto-added Resource References */ public final void clearAutoAddedEntries() { enforceAutoAddedCacheSize(0); } /** * @return Number of auto-generated (and registered) resource references. */ public final int getAutoAddedCacheSize() { return autoAddedQueue == null ? -1 : autoAddedQueue.size(); } /** * @return Number of registered resource references (normal and auto-generated) */ public final int getSize() { return map.size(); } /** * @return the factory that will create the resource reference by using the parsed * {@link org.apache.wicket.request.resource.ResourceReference.Key} */ public IResourceReferenceFactory getResourceReferenceFactory() { return resourceReferenceFactory; } /** * Sets the factory to use when a ResourceReference is not previously * registered and a new instance should be created * * @param resourceReferenceFactory * the factory that will create the resource reference by using the parsed * {@link org.apache.wicket.request.resource.ResourceReference.Key} */ public void setResourceReferenceFactory(IResourceReferenceFactory resourceReferenceFactory) { this.resourceReferenceFactory = resourceReferenceFactory; } }