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.io.Serializable; 020import java.util.ArrayList; 021import java.util.List; 022import java.util.Locale; 023 024import org.apache.wicket.Application; 025import org.apache.wicket.core.util.lang.WicketObjects; 026import org.apache.wicket.markup.head.HeaderItem; 027import org.apache.wicket.util.io.IClusterable; 028import org.apache.wicket.util.lang.Args; 029import org.apache.wicket.util.lang.Objects; 030import org.danekja.java.util.function.serializable.SerializableSupplier; 031 032/** 033 * Reference to a resource. Can be used to reference global resources. 034 * <p> 035 * Even though resource reference is just a factory for resources, it still needs to be identified 036 * by a globally unique identifier, combination of <code>scope</code> and <code>name</code>. Those 037 * are used to generate URLs for resource references. <code>locale</code>, <code>style</code> and 038 * <code>variation</code> are optional fields to allow having specific references for individual 039 * locales, styles and variations. 040 * 041 * @author Matej Knopp 042 * @author Juergen Donnerstag 043 */ 044public abstract class ResourceReference implements IClusterable 045{ 046 private static final long serialVersionUID = 1L; 047 048 private final Key data; 049 050 /** 051 * Creates new {@link ResourceReference} instance. 052 * 053 * @param key 054 * The data making up the resource reference 055 */ 056 public ResourceReference(final Key key) 057 { 058 Args.notNull(key, "key"); 059 060 data = key; 061 } 062 063 /** 064 * Creates new {@link ResourceReference} instance. 065 * 066 * @param scope 067 * mandatory parameter 068 * @param name 069 * mandatory parameter 070 * @param locale 071 * resource locale 072 * @param style 073 * resource style 074 * @param variation 075 * resource variation 076 */ 077 public ResourceReference(Class<?> scope, String name, Locale locale, String style, 078 String variation) 079 { 080 Args.notNull(scope, "scope"); 081 Args.notNull(name, "name"); 082 083 data = new Key(scope.getName(), name, locale, style, variation); 084 } 085 086 /** 087 * Creates new {@link ResourceReference} instance. 088 * 089 * @param scope 090 * mandatory parameter 091 * @param name 092 * mandatory parameter 093 */ 094 public ResourceReference(Class<?> scope, String name) 095 { 096 this(scope, name, null, null, null); 097 } 098 099 /** 100 * Construct. 101 * 102 * @param name 103 * resource name 104 */ 105 public ResourceReference(String name) 106 { 107 this(Application.class, name, null, null, null); 108 } 109 110 /** 111 * @return Gets the data making up the resource reference. They'll be use by 112 * ResourceReferenceRegistry to make up the key under which the resource reference gets 113 * stored. 114 */ 115 public final Key getKey() 116 { 117 return data; 118 } 119 120 /** 121 * @return name 122 */ 123 public String getName() 124 { 125 return data.getName(); 126 } 127 128 /** 129 * returns extension of the resource reference 130 * 131 * @return extension of the resource's name in lower-case or <code>null</code> if there is no 132 * extension 133 */ 134 public final String getExtension() 135 { 136 String name = getName(); 137 138 final int queryAt = name.indexOf('?'); 139 140 // remove query string part 141 if (queryAt != -1) 142 { 143 name = name.substring(0, queryAt); 144 } 145 146 // get start of extension 147 final int extPos = name.lastIndexOf('.'); 148 149 if (extPos == -1) 150 { 151 return null; 152 } 153 154 // return extension 155 return name.substring(extPos + 1).toLowerCase(Locale.ROOT); 156 } 157 158 /** 159 * @return scope 160 */ 161 public Class<?> getScope() 162 { 163 return WicketObjects.resolveClass(data.getScope()); 164 } 165 166 /** 167 * @return locale 168 */ 169 public Locale getLocale() 170 { 171 return data.getLocale(); 172 } 173 174 /** 175 * @return style 176 */ 177 public String getStyle() 178 { 179 return data.getStyle(); 180 } 181 182 /** 183 * @return variation 184 */ 185 public String getVariation() 186 { 187 return data.getVariation(); 188 } 189 190 /** 191 * Can be used to disable registering certain resource references in 192 * {@link ResourceReferenceRegistry}. 193 * 194 * @return <code>true</code> if this reference can be registered, <code>false</code> otherwise. 195 */ 196 public boolean canBeRegistered() 197 { 198 return true; 199 } 200 201 /** 202 * @see java.lang.Object#equals(java.lang.Object) 203 */ 204 @Override 205 public boolean equals(Object obj) 206 { 207 if (this == obj) 208 { 209 return true; 210 } 211 if (obj instanceof ResourceReference == false) 212 { 213 return false; 214 } 215 ResourceReference that = (ResourceReference)obj; 216 return Objects.equal(data, that.data); 217 } 218 219 /** 220 * @see java.lang.Object#hashCode() 221 */ 222 @Override 223 public int hashCode() 224 { 225 return data.hashCode(); 226 } 227 228 /** 229 * Returns the resource. 230 * 231 * @return resource instance 232 */ 233 public abstract IResource getResource(); 234 235 /** 236 * Allows to specify which locale, style and variation values will the generated URL for this 237 * resource reference have. 238 * 239 * @return url attributes 240 */ 241 public UrlAttributes getUrlAttributes() 242 { 243 return new UrlAttributes(getLocale(), getStyle(), getVariation()); 244 } 245 246 /** 247 * Factory method to build a resource reference that uses the provided supplier to return 248 * the resource. 249 * 250 * @param name 251 * The name to use with the resource 252 * @param resourceSupplier 253 * Lambda supplier to build the resource 254 * @return the new resource reference 255 */ 256 public static final ResourceReference of(String name, SerializableSupplier<IResource> resourceSupplier) 257 { 258 return new LambdaResourceReference(name, resourceSupplier); 259 } 260 261 /** 262 * Factory method to build a resource reference that uses the provided supplier to return 263 * the resource. 264 * 265 * @param key 266 * The {@link Key} to use with the resource 267 * @param resourceSupplier 268 * Lambda supplier to build the resource 269 * @return the new resource reference 270 */ 271 public static final ResourceReference of(Key key, SerializableSupplier<IResource> resourceSupplier) 272 { 273 return new LambdaResourceReference(key, resourceSupplier); 274 } 275 276 public static final class LambdaResourceReference extends ResourceReference 277 { 278 private static final long serialVersionUID = 1826862147241009289L; 279 280 final SerializableSupplier<IResource> resourceBuilder; 281 282 public LambdaResourceReference(String name, SerializableSupplier<IResource> resourceBuilder) 283 { 284 super(name); 285 this.resourceBuilder = Args.notNull(resourceBuilder, "resourceBuilder"); 286 } 287 288 public LambdaResourceReference(Key key, SerializableSupplier<IResource> resourceBuilder) 289 { 290 super(key); 291 this.resourceBuilder = Args.notNull(resourceBuilder, "resourceBuilder"); 292 } 293 294 @Override 295 public IResource getResource() 296 { 297 return resourceBuilder.get(); 298 } 299 } 300 301 /** 302 * @see ResourceReference#getUrlAttributes() 303 * 304 * @author Matej Knopp 305 */ 306 public static class UrlAttributes 307 { 308 private final Locale locale; 309 private final String style; 310 private final String variation; 311 312 /** 313 * Construct. 314 * 315 * @param locale 316 * resource locale 317 * @param style 318 * resource style 319 * @param variation 320 * resource variation 321 */ 322 public UrlAttributes(Locale locale, String style, String variation) 323 { 324 this.locale = locale; 325 this.style = style; 326 this.variation = variation; 327 } 328 329 /** 330 * @return locale 331 */ 332 public Locale getLocale() 333 { 334 return locale; 335 } 336 337 /** 338 * @return style 339 */ 340 public String getStyle() 341 { 342 return style; 343 } 344 345 /** 346 * @return variation 347 */ 348 public String getVariation() 349 { 350 return variation; 351 } 352 353 @Override 354 public boolean equals(Object obj) 355 { 356 if (this == obj) 357 { 358 return true; 359 } 360 if (obj instanceof UrlAttributes == false) 361 { 362 return false; 363 } 364 UrlAttributes that = (UrlAttributes)obj; 365 return Objects.equal(getLocale(), that.getLocale()) && 366 Objects.equal(getStyle(), that.getStyle()) && 367 Objects.equal(getVariation(), that.getVariation()); 368 } 369 370 @Override 371 public int hashCode() { 372 // Not using `Objects.hash` for performance reasons 373 int result = locale != null ? locale.hashCode() : 0; 374 result = 31 * result + (style != null ? style.hashCode() : 0); 375 result = 31 * result + (variation != null ? variation.hashCode() : 0); 376 return result; 377 } 378 379 /** 380 * @see java.lang.Object#toString() 381 */ 382 @Override 383 public String toString() 384 { 385 return "locale: " + locale + "; style: " + style + "; variation: " + variation; 386 } 387 } 388 389 /** 390 * A (re-usable) data store for all relevant ResourceReference data 391 */ 392 public static class Key implements Serializable 393 { 394 private static final long serialVersionUID = 1L; 395 396 private final String scope; 397 private final String name; 398 private final Locale locale; 399 private final String style; 400 private final String variation; 401 402 /** 403 * Construct. 404 * 405 * @param reference 406 * resource reference 407 */ 408 public Key(final ResourceReference reference) 409 { 410 this(reference.getScope().getName(), reference.getName(), reference.getLocale(), 411 reference.getStyle(), reference.getVariation()); 412 } 413 414 /** 415 * Construct. 416 * 417 * @param scope 418 * resource scope 419 * @param name 420 * resource name 421 * @param locale 422 * resource locale 423 * @param style 424 * resource style 425 * @param variation 426 * resource variation 427 */ 428 public Key(final String scope, final String name, final Locale locale, final String style, 429 final String variation) 430 { 431 Args.notNull(scope, "scope"); 432 Args.notNull(name, "name"); 433 434 this.scope = scope.intern(); 435 this.name = name.intern(); 436 this.locale = locale; 437 this.style = style != null ? style.intern() : null; 438 this.variation = variation != null ? variation.intern() : null; 439 } 440 441 @Override 442 public boolean equals(final Object obj) 443 { 444 if (this == obj) 445 { 446 return true; 447 } 448 if (obj instanceof Key == false) 449 { 450 return false; 451 } 452 Key that = (Key)obj; 453 return Objects.equal(scope, that.scope) && // 454 Objects.equal(name, that.name) && // 455 Objects.equal(locale, that.locale) && // 456 Objects.equal(style, that.style) && // 457 Objects.equal(variation, that.variation); 458 } 459 460 @Override 461 public int hashCode() { 462 int result = scope != null ? scope.hashCode() : 0; 463 result = 31 * result + (name != null ? name.hashCode() : 0); 464 result = 31 * result + (locale != null ? locale.hashCode() : 0); 465 result = 31 * result + (style != null ? style.hashCode() : 0); 466 result = 31 * result + (variation != null ? variation.hashCode() : 0); 467 return result; 468 } 469 470 /** 471 * Gets scope. 472 * 473 * @return scope 474 */ 475 public final String getScope() 476 { 477 return scope; 478 } 479 480 /** 481 * @return Assuming scope ist a fully qualified class name, than get the associated class 482 */ 483 public final Class<?> getScopeClass() 484 { 485 return WicketObjects.resolveClass(scope); 486 } 487 488 /** 489 * Gets name. 490 * 491 * @return name 492 */ 493 public final String getName() 494 { 495 return name; 496 } 497 498 /** 499 * Gets locale. 500 * 501 * @return locale 502 */ 503 public final Locale getLocale() 504 { 505 return locale; 506 } 507 508 /** 509 * Gets style. 510 * 511 * @return style 512 */ 513 public final String getStyle() 514 { 515 return style; 516 } 517 518 /** 519 * Gets variation. 520 * 521 * @return variation 522 */ 523 public final String getVariation() 524 { 525 return variation; 526 } 527 528 /** 529 * @see java.lang.Object#toString() 530 */ 531 @Override 532 public String toString() 533 { 534 return "scope: " + scope + "; name: " + name + "; locale: " + locale + "; style: " + 535 style + "; variation: " + variation; 536 } 537 } 538 539 @Override 540 public String toString() 541 { 542 return data.toString(); 543 } 544 545 /** 546 * @return the resources this ResourceReference depends on. 547 */ 548 public List<HeaderItem> getDependencies() 549 { 550 return new ArrayList<>(); 551 } 552}