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 < 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}