001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.request.resource;
018
019import java.util.Locale;
020import java.util.Queue;
021import java.util.concurrent.ConcurrentHashMap;
022import java.util.concurrent.ConcurrentLinkedQueue;
023
024import org.apache.wicket.request.resource.ResourceReference.Key;
025import org.apache.wicket.util.lang.Args;
026import org.apache.wicket.util.lang.Generics;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030/**
031 * Allows to register and lookup {@link ResourceReference}s per Application.
032 * 
033 * @see org.apache.wicket.Application#getResourceReferenceRegistry()
034 * @see org.apache.wicket.Application#newResourceReferenceRegistry()
035 * 
036 * @author Matej Knopp
037 * @author Juergen Donnerstag
038 */
039public class ResourceReferenceRegistry
040{
041        /** Log for reporting. */
042        private static final Logger log = LoggerFactory.getLogger(ResourceReferenceRegistry.class);
043
044        // Scan classes and its superclasses for static ResourceReference fields. For each
045        // RR field found, the callback method is called and the RR gets registered. It's kind of
046        // auto-register all RRs in your Component hierarchy.
047        private ClassScanner scanner = new ClassScanner()
048        {
049                @Override
050                boolean foundResourceReference(final ResourceReference reference)
051                {
052                        // register the RR found (static field of Scope class)
053                        return registerResourceReference(reference);
054                }
055        };
056
057        // The Map (registry) maintaining the resource references
058        private final ConcurrentHashMap<Key, ResourceReference> map = Generics.newConcurrentHashMap();
059
060        // If combinations of parameters (Key) have no registered resource reference yet, a default
061        // resource reference can be created and added to the registry. The following list keeps track
062        // of all auto added references.
063        private Queue<Key> autoAddedQueue;
064
065        // max entries. If the queue is full and new references are auto generated, references are
066        // removed starting with the first entry and unregistered from the registry.
067        private int autoAddedCapacity = 1000;
068
069        /**
070         * A simple implementation of {@link IResourceReferenceFactory} that creates
071         * {@link PackageResourceReference}
072         */
073        public static class DefaultResourceReferenceFactory implements IResourceReferenceFactory
074        {
075                @Override
076                public ResourceReference create(Key key)
077                {
078                        ResourceReference result = null;
079                        if (PackageResource.exists(key))
080                        {
081                                result = new PackageResourceReference(key);
082                        }
083                        return result;
084                }
085        }
086
087        /**
088         * The factory to use when a ResourceReference is not previously
089         * registered and a new instance should be create
090         */
091        private IResourceReferenceFactory resourceReferenceFactory;
092
093        /**
094         * Constructor.
095         *
096         * <p>Uses DefaultResourceReferenceFactory to create ResourceReference when there is
097         * no registered one for the requested attributes</p>
098         */
099        public ResourceReferenceRegistry()
100        {
101                this(new DefaultResourceReferenceFactory());
102        }
103
104        /**
105         * Constructor
106         *
107         * @param resourceReferenceFactory
108         *      The factory that will create ResourceReference by Key when there is no registered one
109         */
110        public ResourceReferenceRegistry(IResourceReferenceFactory resourceReferenceFactory)
111        {
112                this.resourceReferenceFactory = Args.notNull(resourceReferenceFactory, "resourceReferenceFactory");
113
114                // Initial the auto-add list for a maximum of 1000 entries
115                setAutoAddedCapacity(autoAddedCapacity);
116        }
117
118        /**
119         * Registers the given {@link ResourceReference}.
120         * <p>
121         * {@link ResourceReference#canBeRegistered()} must return <code>true</code>. Else, the resource
122         * reference will not be registered.
123         * 
124         * @param reference
125         *      the reference to register
126         * @return {@code true} if the resource was registered successfully or has been registered previously
127         *         already.
128         */
129        public final boolean registerResourceReference(final ResourceReference reference)
130        {
131                return null != _registerResourceReference(reference);
132        }
133
134        /**
135         * Registers the given {@link ResourceReference}.
136         * <p>
137         * {@link ResourceReference#canBeRegistered()} must return <code>true</code>. Else, the resource
138         * reference will not be registered.
139         * 
140         * @param reference
141         *      the reference to register
142         * @return {@code true} if the resource was registered successfully or has been registered previously
143         *         already.
144         */
145        private Key _registerResourceReference(final ResourceReference reference)
146        {
147                Args.notNull(reference, "reference");
148
149                if (reference.canBeRegistered())
150                {
151                        Key key = reference.getKey();
152                        map.putIfAbsent(key, reference);
153                        return key;
154                }
155
156                log.warn("{} cannot be added to the registry.", reference.getClass().getName());
157                return null;
158        }
159
160        /**
161         * Unregisters a {@link ResourceReference} by its identifier.
162         * 
163         * @param key
164         *            the {@link ResourceReference}'s identifier
165         * @return The removed ResourceReference or {@code null} if the registry did not contain an entry for this key.
166         */
167        public final ResourceReference unregisterResourceReference(final Key key)
168        {
169                Args.notNull(key, "key");
170
171                // remove from registry
172                ResourceReference removed = map.remove(key);
173
174                // remove from auto-added list, in case the RR was auto-added
175                if (autoAddedQueue != null)
176                {
177                        autoAddedQueue.remove(key);
178                }
179
180                return removed;
181        }
182
183        /**
184         * Get a resource reference matching the parameters from the registry or if not found and
185         * requested, create an default resource reference and add it to the registry.
186         * <p>
187         * Part of the search is scanning the class (scope) and it's superclass for static
188         * ResourceReference fields. Found fields get registered automatically (but are different from
189         * auto-generated ResourceReferences).
190         * 
191         * @see #createDefaultResourceReference(org.apache.wicket.request.resource.ResourceReference.Key)
192         * @see ClassScanner
193         * 
194         * @param scope
195         *            The scope of resource reference (e.g. the Component's class)
196         * @param name
197         *            The name of resource reference (e.g. filename)
198         * @param locale
199         *            see Component
200         * @param style
201         *            see Component
202         * @param variation
203         *            see Component
204         * @param strict
205         *            If true, "weaker" combination of scope, name, locale etc. are not tested
206         * @param createIfNotFound
207         *            If true a default resource reference is created if no entry can be found in the
208         *            registry. The newly created resource reference will be added to the registry.
209         * @return Either the resource reference found in the registry or, if requested, a resource
210         *         reference automatically created based on the parameters provided. The automatically
211         *         created resource reference will automatically be added to the registry.
212         */
213        public final ResourceReference getResourceReference(final Class<?> scope, final String name,
214                final Locale locale, final String style, final String variation, final boolean strict,
215                final boolean createIfNotFound)
216        {
217                return getResourceReference(new Key(scope.getName(), name, locale, style, variation),
218                        strict, createIfNotFound);
219        }
220
221        /**
222         * Get a resource reference matching the parameters from the registry or if not found and
223         * requested, create an default resource reference and add it to the registry.
224         * <p>
225         * Part of the search is scanning the class (scope) and it's superclass for static
226         * ResourceReference fields. Found fields get registered automatically (but are different from
227         * auto-generated ResourceReferences).
228         * 
229         * @see #createDefaultResourceReference(org.apache.wicket.request.resource.ResourceReference.Key)
230         * @see ClassScanner
231         * 
232         * @param key
233         *            The data making up the resource reference
234         * @param strict
235         *            If true, "weaker" combination of scope, name, locale etc. are not tested
236         * @param createIfNotFound
237         *            If true a default resource reference is created if no entry can be found in the
238         *            registry. The newly created resource reference will be added to the registry.
239         * @return Either the resource reference found in the registry or, if requested, a resource
240         *         reference automatically created based on the parameters provided. The automatically
241         *         created resource reference will automatically be added to the registry.
242         */
243        public final ResourceReference getResourceReference(final Key key, final boolean strict,
244                final boolean createIfNotFound)
245        {
246                ResourceReference resource = _getResourceReference(key.getScope(), key.getName(),
247                        key.getLocale(), key.getStyle(), key.getVariation(), strict);
248
249                // Nothing found so far?
250                if (resource == null)
251                {
252                        // Scan the class (scope) and it's super classes for static fields containing resource
253                        // references. Such resources are registered as normal resource reference (not
254                        // auto-added).
255                        if (scanner.scanClass(key.getScopeClass()) > 0)
256                        {
257                                // At least one new resource reference got registered => Search the registry again
258                                resource = _getResourceReference(key.getScope(), key.getName(), key.getLocale(),
259                                        key.getStyle(), key.getVariation(), strict);
260                        }
261
262                        // Still nothing found => Shall a new reference be auto-created?
263                        if ((resource == null) && createIfNotFound)
264                        {
265                                resource = addDefaultResourceReference(key);
266                        }
267                }
268
269                return resource;
270        }
271
272        /**
273         * Get a resource reference matching the parameters from the registry.
274         * 
275         * @param scope
276         *            The scope of resource reference (e.g. the Component's class)
277         * @param name
278         *            The name of resource reference (e.g. filename)
279         * @param locale
280         *            see Component
281         * @param style
282         *            see Component
283         * @param variation
284         *            see Component
285         * @param strict
286         *            If true, "weaker" combination of scope, name, locale etc. are not tested
287         * @return Either the resource reference found in the registry or null if not found
288         */
289        private ResourceReference _getResourceReference(final String scope, final String name,
290                final Locale locale, final String style, final String variation, final boolean strict)
291        {
292                // Create a registry key containing all of the relevant attributes
293                Key key = new Key(scope, name, locale, style, variation);
294
295                // Get resource reference matching exactly the attrs provided
296                ResourceReference res = map.get(key);
297                if ((res != null) || strict)
298                {
299                        return res;
300                }
301
302                res = _getResourceReference(scope, name, locale, style, null, true);
303                if (res == null)
304                {
305                        res = _getResourceReference(scope, name, locale, null, variation, true);
306                }
307                if (res == null)
308                {
309                        res = _getResourceReference(scope, name, locale, null, null, true);
310                }
311                if (res == null)
312                {
313                        res = _getResourceReference(scope, name, null, style, variation, true);
314                }
315                if (res == null)
316                {
317                        res = _getResourceReference(scope, name, null, style, null, true);
318                }
319                if (res == null)
320                {
321                        res = _getResourceReference(scope, name, null, null, variation, true);
322                }
323                if (res == null)
324                {
325                        res = _getResourceReference(scope, name, null, null, null, true);
326                }
327                return res;
328        }
329
330        /**
331         * Creates a default resource reference and registers it.
332         * 
333         * @param key
334         *      the data making up the resource reference
335         * @return The default resource created
336         */
337        private ResourceReference addDefaultResourceReference(final Key key)
338        {
339                // Can be subclassed to create other than PackagedResourceReference
340                ResourceReference reference = createDefaultResourceReference(key);
341
342                if (reference != null)
343                {
344                        // number of RRs which can be auto-added is restricted (cache size). Remove entries, and
345                        // unregister excessive ones, if needed.
346                        enforceAutoAddedCacheSize(getAutoAddedCapacity());
347
348                        // Register the new RR
349                        _registerResourceReference(reference);
350
351                        // Add it to the auto-added list
352                        if (autoAddedQueue != null)
353                        {
354                                autoAddedQueue.add(key);
355                        }
356                }
357                else
358                {
359                        log.warn("A ResourceReference wont be created for a resource with key [{}] because it cannot be located.", key);
360                }
361                return reference;
362        }
363
364        /**
365         * The number of {@link ResourceReference}s which can be auto-added is restricted (cache size). Remove entries, and
366         * unregister excessive ones, if needed.
367         * 
368         * @param maxSize
369         *            Remove all entries, head first, until auto-added cache is smaller than maxSize.
370         */
371        private void enforceAutoAddedCacheSize(int maxSize)
372        {
373                if (autoAddedQueue != null)
374                {
375                        while (autoAddedQueue.size() > maxSize)
376                        {
377                                // remove entry from auto-added list
378                                Key first = autoAddedQueue.remove();
379
380                                // remove entry from registry
381                                map.remove(first);
382                        }
383                }
384        }
385
386        /**
387         * Creates a default resource reference in case no registry entry and it was requested to create
388         * one.
389         * <p>
390         * A {@link PackageResourceReference} will be created by default
391         * 
392         * @param key
393         *      the data making up the resource reference
394         * @return The {@link ResourceReference} created or {@code null} if not successful
395         */
396        protected ResourceReference createDefaultResourceReference(final Key key)
397        {
398                IResourceReferenceFactory factory = getResourceReferenceFactory();
399                if (factory == null)
400                {
401                        factory = new DefaultResourceReferenceFactory();
402                }
403                return factory.create(key);
404        }
405
406        /**
407         * Set the cache size in number of entries
408         * 
409         * @param autoAddedCapacity
410         *            A value &lt; 0 will disable aging of auto-create resource references. They will be
411         *            created, added to the registry and live their until manually removed or the
412         *            application shuts down.
413         */
414        public final void setAutoAddedCapacity(final int autoAddedCapacity)
415        {
416                // Disable aging of auto-added references?
417                if (autoAddedCapacity < 0)
418                {
419                        // unregister all auto-added references
420                        clearAutoAddedEntries();
421
422                        // disable aging from now on
423                        autoAddedQueue = null;
424                }
425                else
426                {
427                        this.autoAddedCapacity = autoAddedCapacity;
428
429                        if (autoAddedQueue == null)
430                        {
431                                autoAddedQueue = new ConcurrentLinkedQueue<Key>();
432                        }
433                        else
434                        {
435                                // remove all extra entries if necessary
436                                enforceAutoAddedCacheSize(autoAddedCapacity);
437                        }
438                }
439        }
440
441        /**
442         * Gets cache size in number of entries
443         * 
444         * @return capacity
445         */
446        public final int getAutoAddedCapacity()
447        {
448                return autoAddedCapacity;
449        }
450
451        /**
452         * Unregisters all auto-added Resource References
453         */
454        public final void clearAutoAddedEntries()
455        {
456                enforceAutoAddedCacheSize(0);
457        }
458
459        /**
460         * @return Number of auto-generated (and registered) resource references.
461         */
462        public final int getAutoAddedCacheSize()
463        {
464                return autoAddedQueue == null ? -1 : autoAddedQueue.size();
465        }
466
467        /**
468         * @return Number of registered resource references (normal and auto-generated)
469         */
470        public final int getSize()
471        {
472                return map.size();
473        }
474
475        /**
476         * @return the factory that will create the resource reference by using the parsed
477         *          {@link org.apache.wicket.request.resource.ResourceReference.Key}
478         */
479        public IResourceReferenceFactory getResourceReferenceFactory()
480        {
481                return resourceReferenceFactory;
482        }
483
484        /**
485         * Sets the factory to use when a ResourceReference is not previously
486         * registered and a new instance should be created
487         *
488         * @param resourceReferenceFactory
489         *          the factory that will create the resource reference by using the parsed
490         *          {@link org.apache.wicket.request.resource.ResourceReference.Key}
491         */
492        public void setResourceReferenceFactory(IResourceReferenceFactory resourceReferenceFactory)
493        {
494                this.resourceReferenceFactory = resourceReferenceFactory;
495        }
496}