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.markup.html.image.resource; 018 019import java.util.Locale; 020 021import org.apache.wicket.Application; 022import org.apache.wicket.Component; 023import org.apache.wicket.IResourceFactory; 024import org.apache.wicket.MarkupContainer; 025import org.apache.wicket.WicketRuntimeException; 026import org.apache.wicket.markup.ComponentTag; 027import org.apache.wicket.markup.html.border.Border; 028import org.apache.wicket.request.cycle.RequestCycle; 029import org.apache.wicket.request.mapper.parameter.PageParameters; 030import org.apache.wicket.request.resource.IResource; 031import org.apache.wicket.request.resource.IResource.Attributes; 032import org.apache.wicket.request.resource.PackageResourceReference; 033import org.apache.wicket.request.resource.ResourceReference; 034import org.apache.wicket.util.io.IClusterable; 035import org.apache.wicket.util.lang.Objects; 036import org.apache.wicket.util.parse.metapattern.Group; 037import org.apache.wicket.util.parse.metapattern.MetaPattern; 038import org.apache.wicket.util.parse.metapattern.OptionalMetaPattern; 039import org.apache.wicket.util.parse.metapattern.parsers.MetaPatternParser; 040import org.apache.wicket.util.string.Strings; 041 042 043/** 044 * THIS CLASS IS INTENDED FOR INTERNAL USE IN IMPLEMENTING LOCALE SENSITIVE COMPONENTS THAT USE 045 * IMAGE RESOURCES AND SHOULD NOT BE USED DIRECTLY BY END-USERS. 046 * <p> 047 * This class contains the logic for extracting static image resources referenced by the SRC 048 * attribute of component tags and keeping these static image resources in sync with the component 049 * locale. 050 * <p> 051 * If no image is specified by the SRC attribute of an IMG tag, then any VALUE attribute is 052 * inspected. If there is a VALUE attribute, it must be of the form 053 * "[factoryName]:[sharedImageName]?:[specification]". [factoryName] is the name of a resource 054 * factory that has been added to Application (for example, DefaultButtonImageResourceFactory is 055 * installed by default under the name "buttonFactory"). The [sharedImageName] value is optional and 056 * gives a name under which a given generated image is shared. For example, a cancel button image 057 * generated by the VALUE attribute "buttonFactory:cancelButton:Cancel" is shared under the name 058 * "cancelButton" and this specification will cause a component to reference the same image resource 059 * no matter what page it appears on, which is a very convenient and efficient way to create and 060 * share images. The [specification] string which follows the second colon is passed directly to the 061 * image factory and its format is dependent on the specific image factory. For details on the 062 * default buttonFactory, see 063 * {@link org.apache.wicket.markup.html.image.resource.DefaultButtonImageResourceFactory}. 064 * <p> 065 * Finally, if there is no SRC attribute and no VALUE attribute, the Image component's model is 066 * inspected. If the model contains a resource or resource reference, this image is used, otherwise 067 * the model is converted to a String and that value is used as a path to load the image. 068 * 069 * @author Jonathan Locke 070 */ 071public final class LocalizedImageResource implements IClusterable 072{ 073 private static final long serialVersionUID = 1L; 074 075 /** 076 * What kind of resource it is. TRUE==Resource is set, FALSE==ResourceReference is set, null 077 * none 078 */ 079 private Boolean resourceKind; 080 081 /** The component that is referencing this image resource */ 082 private final Component component; 083 084 /** The image resource this image component references */ 085 private IResource resource; 086 087 /** The resource reference */ 088 private ResourceReference resourceReference; 089 090 /** The resource parameters */ 091 private PageParameters resourceParameters; 092 093 /** The locale of the image resource */ 094 private Locale locale; 095 096 /** The style of the image resource */ 097 private String style; 098 099 /** The component's variation (of the style) */ 100 private String variation; 101 102 /** 103 * Parses image value specifications of the form "[factoryName]: 104 * [shared-image-name]?:[specification]" 105 * 106 * @author Jonathan Locke 107 */ 108 private static final class ImageValueParser extends MetaPatternParser 109 { 110 /** Factory name */ 111 private static final Group factoryName = new Group(MetaPattern.VARIABLE_NAME); 112 113 /** Image reference name */ 114 private static final Group imageReferenceName = new Group(MetaPattern.VARIABLE_NAME); 115 116 /** Factory specification string */ 117 private static final Group specification = new Group(MetaPattern.ANYTHING_NON_EMPTY); 118 119 /** Meta pattern. */ 120 private static final MetaPattern pattern = new MetaPattern(factoryName, MetaPattern.COLON, 121 new OptionalMetaPattern(new MetaPattern[] { imageReferenceName }), MetaPattern.COLON, 122 specification); 123 124 /** 125 * Construct. 126 * 127 * @param input 128 * to parse 129 */ 130 private ImageValueParser(final CharSequence input) 131 { 132 super(pattern, input); 133 } 134 135 /** 136 * @return The factory name 137 */ 138 private String getFactoryName() 139 { 140 return factoryName.get(matcher()); 141 } 142 143 /** 144 * @return Returns the imageReferenceName. 145 */ 146 private String getImageReferenceName() 147 { 148 return imageReferenceName.get(matcher()); 149 } 150 151 /** 152 * @return Returns the specification. 153 */ 154 private String getSpecification() 155 { 156 return specification.get(matcher()); 157 } 158 } 159 160 /** 161 * Constructor 162 * 163 * @param component 164 * The component that owns this localized image resource 165 */ 166 public LocalizedImageResource(final Component component) 167 { 168 this.component = component; 169 locale = component.getLocale(); 170 style = component.getStyle(); 171 variation = component.getVariation(); 172 } 173 174 /** 175 * Binds this resource if it is shared 176 */ 177 public final void bind() 178 { 179 // If we have a resource reference 180 if (resourceReference != null && resourceReference.canBeRegistered() && 181 Application.exists()) 182 { 183 // Bind the reference to the application 184 Application.get() 185 .getResourceReferenceRegistry() 186 .registerResourceReference(resourceReference); 187 } 188 } 189 190 /** 191 * @param parameters 192 * page parameters 193 */ 194 public final void onResourceRequested(PageParameters parameters) 195 { 196 bind(); 197 RequestCycle requestCycle = RequestCycle.get(); 198 Attributes attributes = new Attributes(requestCycle.getRequest(), 199 requestCycle.getResponse(), parameters); 200 resource.respond(attributes); 201 } 202 203 /** 204 * @param resource 205 * The resource to set. 206 */ 207 public final void setResource(final IResource resource) 208 { 209 if (this.resource != resource) 210 { 211 resourceKind = Boolean.TRUE; 212 this.resource = resource; 213 } 214 } 215 216 /** 217 * @param resourceReference 218 * The resource to set. 219 */ 220 public final void setResourceReference(final ResourceReference resourceReference) 221 { 222 setResourceReference(resourceReference, resourceParameters); 223 } 224 225 /** 226 * @return true if it has a resourceReference. (it points to a shared resource) 227 */ 228 public final boolean isStateless() 229 { 230 return resourceReference != null; 231 } 232 233 /** 234 * @param resourceReference 235 * The resource to set. 236 * @param resourceParameters 237 * The resource parameters for the shared resource 238 */ 239 public final void setResourceReference(final ResourceReference resourceReference, 240 final PageParameters resourceParameters) 241 { 242 if (resourceReference != this.resourceReference) 243 { 244 resourceKind = Boolean.FALSE; 245 this.resourceReference = resourceReference; 246 } 247 this.resourceParameters = resourceParameters; 248 bind(); 249 } 250 251 /** 252 * @param tag 253 * The tag to inspect for an optional src attribute that might reference an image. 254 * @throws WicketRuntimeException 255 * Thrown if an image is required by the caller, but none can be found. 256 */ 257 public final void setSrcAttribute(final ComponentTag tag) 258 { 259 // If locale has changed from the initial locale used to attach image 260 // resource, then we need to reload the resource in the new locale 261 Locale l = component.getLocale(); 262 String s = component.getStyle(); 263 String v = component.getVariation(); 264 if (resourceKind == null && 265 (!Objects.equal(locale, l) || !Objects.equal(style, s) || !Objects.equal(variation, v))) 266 { 267 // Get new component locale and style 268 locale = l; 269 style = s; 270 variation = v; 271 272 // Invalidate current resource so it will be reloaded/recomputed 273 resourceReference = null; 274 resource = null; 275 } 276 else 277 { 278 // TODO post 1.2: should we have support for locale changes when the 279 // resource reference (or resource??) is set manually.. 280 // We should get a new resource reference for the current locale 281 // then that points to the same resource but with another locale if 282 // it exists. Something like 283 // SharedResource.getResourceReferenceForLocale(resourceReference); 284 } 285 286 // check if the model contains a resource, if so, load the resource from 287 // the model. 288 Object modelObject = component.getDefaultModelObject(); 289 if (modelObject instanceof ResourceReference) 290 { 291 resourceReference = (ResourceReference)modelObject; 292 } 293 else if (modelObject instanceof IResource) 294 { 295 resource = (IResource)modelObject; 296 } 297 298 // Need to load image resource for this component? 299 if (resource == null && resourceReference == null) 300 { 301 // Get SRC attribute of tag 302 final CharSequence src = tag.getAttribute("src"); 303 if (src != null) 304 { 305 // Try to load static image 306 loadStaticImage(src.toString()); 307 } 308 else 309 { 310 // Get VALUE attribute of tag 311 final CharSequence value = tag.getAttribute("value"); 312 if (value != null) 313 { 314 // Try to generate an image using an image factory 315 newImage(value); 316 } 317 else 318 { 319 // Load static image using model object as the path 320 loadStaticImage(component.getDefaultModelObjectAsString()); 321 } 322 } 323 } 324 325 // Get URL for resource 326 final CharSequence url; 327 if (resourceReference != null) 328 { 329 // Create URL to resource 330 url = RequestCycle.get().urlFor(resourceReference, resourceParameters); 331 } 332 else 333 { 334 // Create URL to component 335 url = component.urlForListener(resourceParameters); 336 } 337 338 // Set the SRC attribute to point to the component or shared resource 339 tag.put("src", url); 340 } 341 342 /** 343 * @param application 344 * The application 345 * @param factoryName 346 * The name of the image resource factory 347 * @return The resource factory 348 * @throws WicketRuntimeException 349 * Thrown if factory cannot be found 350 */ 351 private IResourceFactory getResourceFactory(final Application application, 352 final String factoryName) 353 { 354 final IResourceFactory factory = application.getResourceSettings().getResourceFactory( 355 factoryName); 356 357 // Found factory? 358 if (factory == null) 359 { 360 throw new WicketRuntimeException("Could not find image resource factory named " + 361 factoryName); 362 } 363 return factory; 364 } 365 366 367 static class SimpleStaticResourceReference extends ResourceReference 368 { 369 final IResource resource; 370 371 public SimpleStaticResourceReference(Class<?> scope, String name, Locale locale, 372 String style, String variation, IResource resource) 373 { 374 super(scope, name, locale, style, variation); 375 this.resource = resource; 376 } 377 378 private static final long serialVersionUID = 1L; 379 380 @Override 381 public IResource getResource() 382 { 383 return resource; 384 } 385 386 } 387 388 /** 389 * Tries to load static image at the given path and throws an exception if the image cannot be 390 * located. 391 * 392 * @param path 393 * The path to the image 394 * @throws WicketRuntimeException 395 * Thrown if the image cannot be located 396 */ 397 private void loadStaticImage(final String path) 398 { 399 MarkupContainer parent = component.findParentWithAssociatedMarkup(); 400 if (parent instanceof Border) 401 { 402 parent = parent.getParent(); 403 } 404 final Class<?> scope = parent.getClass(); 405 resourceReference = new PackageResourceReference(scope, path, locale, style, variation); 406 bind(); 407 } 408 409 /** 410 * Generates an image resource based on the attribute values on tag 411 * 412 * @param value 413 * The value to parse 414 */ 415 private void newImage(final CharSequence value) 416 { 417 // Parse value 418 final ImageValueParser valueParser = new ImageValueParser(value); 419 420 // Does value match parser? 421 if (valueParser.matches()) 422 { 423 final String imageReferenceName = valueParser.getImageReferenceName(); 424 final String specification = Strings.replaceHtmlEscapeNumber(valueParser.getSpecification()); 425 final String factoryName = valueParser.getFactoryName(); 426 final Application application = component.getApplication(); 427 428 // Do we have a reference? 429 if (!Strings.isEmpty(imageReferenceName)) 430 { 431 // Is resource already available via the application? 432 if (application.getResourceReferenceRegistry().getResourceReference( 433 Application.class, imageReferenceName, locale, style, variation, true, false) == null) 434 { 435 // Resource not available yet, so create it with factory and 436 // share via Application 437 final IResource imageResource = getResourceFactory(application, factoryName).newResource( 438 specification, locale, style, variation); 439 440 ResourceReference ref = new SimpleStaticResourceReference(Application.class, 441 imageReferenceName, locale, style, variation, imageResource); 442 443 application.getResourceReferenceRegistry().registerResourceReference(ref); 444 } 445 446 // Create resource reference 447 resourceReference = new PackageResourceReference(Application.class, 448 imageReferenceName, locale, style, variation); 449 } 450 else 451 { 452 resource = getResourceFactory(application, factoryName).newResource(specification, 453 locale, style, variation); 454 } 455 } 456 else 457 { 458 throw new WicketRuntimeException( 459 "Could not generate image for value attribute '" + 460 value + 461 "'. Was expecting a value attribute of the form \"[resourceFactoryName]:[resourceReferenceName]?:[factorySpecification]\"."); 462 } 463 } 464 465 /** 466 * return the resource 467 * 468 * @return resource or <code>null</code> if there is none 469 */ 470 public final IResource getResource() 471 { 472 return resource; 473 } 474 475 /** 476 * return the resource 477 * 478 * @return resource or <code>null</code> if there is none 479 */ 480 public final ResourceReference getResourceReference() 481 { 482 return resourceReference; 483 } 484}