/* * 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.markup.html.image.resource; import java.util.Locale; import org.apache.wicket.Application; import org.apache.wicket.Component; import org.apache.wicket.IResourceFactory; import org.apache.wicket.MarkupContainer; import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.html.border.Border; import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.request.resource.IResource; import org.apache.wicket.request.resource.IResource.Attributes; import org.apache.wicket.request.resource.PackageResourceReference; import org.apache.wicket.request.resource.ResourceReference; import org.apache.wicket.util.io.IClusterable; import org.apache.wicket.util.lang.Objects; import org.apache.wicket.util.parse.metapattern.Group; import org.apache.wicket.util.parse.metapattern.MetaPattern; import org.apache.wicket.util.parse.metapattern.OptionalMetaPattern; import org.apache.wicket.util.parse.metapattern.parsers.MetaPatternParser; import org.apache.wicket.util.string.Strings; /** * THIS CLASS IS INTENDED FOR INTERNAL USE IN IMPLEMENTING LOCALE SENSITIVE COMPONENTS THAT USE * IMAGE RESOURCES AND SHOULD NOT BE USED DIRECTLY BY END-USERS. * <p> * This class contains the logic for extracting static image resources referenced by the SRC * attribute of component tags and keeping these static image resources in sync with the component * locale. * <p> * If no image is specified by the SRC attribute of an IMG tag, then any VALUE attribute is * inspected. If there is a VALUE attribute, it must be of the form * "[factoryName]:[sharedImageName]?:[specification]". [factoryName] is the name of a resource * factory that has been added to Application (for example, DefaultButtonImageResourceFactory is * installed by default under the name "buttonFactory"). The [sharedImageName] value is optional and * gives a name under which a given generated image is shared. For example, a cancel button image * generated by the VALUE attribute "buttonFactory:cancelButton:Cancel" is shared under the name * "cancelButton" and this specification will cause a component to reference the same image resource * no matter what page it appears on, which is a very convenient and efficient way to create and * share images. The [specification] string which follows the second colon is passed directly to the * image factory and its format is dependent on the specific image factory. For details on the * default buttonFactory, see * {@link org.apache.wicket.markup.html.image.resource.DefaultButtonImageResourceFactory}. * <p> * Finally, if there is no SRC attribute and no VALUE attribute, the Image component's model is * inspected. If the model contains a resource or resource reference, this image is used, otherwise * the model is converted to a String and that value is used as a path to load the image. * * @author Jonathan Locke */ public final class LocalizedImageResource implements IClusterable { private static final long serialVersionUID = 1L; /** * What kind of resource it is. TRUE==Resource is set, FALSE==ResourceReference is set, null * none */ private Boolean resourceKind; /** The component that is referencing this image resource */ private final Component component; /** The image resource this image component references */ private IResource resource; /** The resource reference */ private ResourceReference resourceReference; /** The resource parameters */ private PageParameters resourceParameters; /** The locale of the image resource */ private Locale locale; /** The style of the image resource */ private String style; /** The component's variation (of the style) */ private String variation; /** * Parses image value specifications of the form "[factoryName]: * [shared-image-name]?:[specification]" * * @author Jonathan Locke */ private static final class ImageValueParser extends MetaPatternParser { /** Factory name */ private static final Group factoryName = new Group(MetaPattern.VARIABLE_NAME); /** Image reference name */ private static final Group imageReferenceName = new Group(MetaPattern.VARIABLE_NAME); /** Factory specification string */ private static final Group specification = new Group(MetaPattern.ANYTHING_NON_EMPTY); /** Meta pattern. */ private static final MetaPattern pattern = new MetaPattern(factoryName, MetaPattern.COLON, new OptionalMetaPattern(new MetaPattern[] { imageReferenceName }), MetaPattern.COLON, specification); /** * Construct. * * @param input * to parse */ private ImageValueParser(final CharSequence input) { super(pattern, input); } /** * @return The factory name */ private String getFactoryName() { return factoryName.get(matcher()); } /** * @return Returns the imageReferenceName. */ private String getImageReferenceName() { return imageReferenceName.get(matcher()); } /** * @return Returns the specification. */ private String getSpecification() { return specification.get(matcher()); } } /** * Constructor * * @param component * The component that owns this localized image resource */ public LocalizedImageResource(final Component component) { this.component = component; locale = component.getLocale(); style = component.getStyle(); variation = component.getVariation(); } /** * Binds this resource if it is shared */ public final void bind() { // If we have a resource reference if (resourceReference != null && resourceReference.canBeRegistered() && Application.exists()) { // Bind the reference to the application Application.get() .getResourceReferenceRegistry() .registerResourceReference(resourceReference); } } /** * @param parameters * page parameters */ public final void onResourceRequested(PageParameters parameters) { bind(); RequestCycle requestCycle = RequestCycle.get(); Attributes attributes = new Attributes(requestCycle.getRequest(), requestCycle.getResponse(), parameters); resource.respond(attributes); } /** * @param resource * The resource to set. */ public final void setResource(final IResource resource) { if (this.resource != resource) { resourceKind = Boolean.TRUE; this.resource = resource; } } /** * @param resourceReference * The resource to set. */ public final void setResourceReference(final ResourceReference resourceReference) { setResourceReference(resourceReference, resourceParameters); } /** * @return true if it has a resourceReference. (it points to a shared resource) */ public final boolean isStateless() { return resourceReference != null; } /** * @param resourceReference * The resource to set. * @param resourceParameters * The resource parameters for the shared resource */ public final void setResourceReference(final ResourceReference resourceReference, final PageParameters resourceParameters) { if (resourceReference != this.resourceReference) { resourceKind = Boolean.FALSE; this.resourceReference = resourceReference; } this.resourceParameters = resourceParameters; bind(); } /** * @param tag * The tag to inspect for an optional src attribute that might reference an image. * @throws WicketRuntimeException * Thrown if an image is required by the caller, but none can be found. */ public final void setSrcAttribute(final ComponentTag tag) { // If locale has changed from the initial locale used to attach image // resource, then we need to reload the resource in the new locale Locale l = component.getLocale(); String s = component.getStyle(); String v = component.getVariation(); if (resourceKind == null && (!Objects.equal(locale, l) || !Objects.equal(style, s) || !Objects.equal(variation, v))) { // Get new component locale and style locale = l; style = s; variation = v; // Invalidate current resource so it will be reloaded/recomputed resourceReference = null; resource = null; } else { // TODO post 1.2: should we have support for locale changes when the // resource reference (or resource??) is set manually.. // We should get a new resource reference for the current locale // then that points to the same resource but with another locale if // it exists. Something like // SharedResource.getResourceReferenceForLocale(resourceReference); } // check if the model contains a resource, if so, load the resource from // the model. Object modelObject = component.getDefaultModelObject(); if (modelObject instanceof ResourceReference) { resourceReference = (ResourceReference)modelObject; } else if (modelObject instanceof IResource) { resource = (IResource)modelObject; } // Need to load image resource for this component? if (resource == null && resourceReference == null) { // Get SRC attribute of tag final CharSequence src = tag.getAttribute("src"); if (src != null) { // Try to load static image loadStaticImage(src.toString()); } else { // Get VALUE attribute of tag final CharSequence value = tag.getAttribute("value"); if (value != null) { // Try to generate an image using an image factory newImage(value); } else { // Load static image using model object as the path loadStaticImage(component.getDefaultModelObjectAsString()); } } } // Get URL for resource final CharSequence url; if (resourceReference != null) { // Create URL to resource url = RequestCycle.get().urlFor(resourceReference, resourceParameters); } else { // Create URL to component url = component.urlForListener(resourceParameters); } // Set the SRC attribute to point to the component or shared resource tag.put("src", url); } /** * @param application * The application * @param factoryName * The name of the image resource factory * @return The resource factory * @throws WicketRuntimeException * Thrown if factory cannot be found */ private IResourceFactory getResourceFactory(final Application application, final String factoryName) { final IResourceFactory factory = application.getResourceSettings().getResourceFactory( factoryName); // Found factory? if (factory == null) { throw new WicketRuntimeException("Could not find image resource factory named " + factoryName); } return factory; } static class SimpleStaticResourceReference extends ResourceReference { final IResource resource; public SimpleStaticResourceReference(Class<?> scope, String name, Locale locale, String style, String variation, IResource resource) { super(scope, name, locale, style, variation); this.resource = resource; } private static final long serialVersionUID = 1L; @Override public IResource getResource() { return resource; } } /** * Tries to load static image at the given path and throws an exception if the image cannot be * located. * * @param path * The path to the image * @throws WicketRuntimeException * Thrown if the image cannot be located */ private void loadStaticImage(final String path) { MarkupContainer parent = component.findParentWithAssociatedMarkup(); if (parent instanceof Border) { parent = parent.getParent(); } final Class<?> scope = parent.getClass(); resourceReference = new PackageResourceReference(scope, path, locale, style, variation); bind(); } /** * Generates an image resource based on the attribute values on tag * * @param value * The value to parse */ private void newImage(final CharSequence value) { // Parse value final ImageValueParser valueParser = new ImageValueParser(value); // Does value match parser? if (valueParser.matches()) { final String imageReferenceName = valueParser.getImageReferenceName(); final String specification = Strings.replaceHtmlEscapeNumber(valueParser.getSpecification()); final String factoryName = valueParser.getFactoryName(); final Application application = component.getApplication(); // Do we have a reference? if (!Strings.isEmpty(imageReferenceName)) { // Is resource already available via the application? if (application.getResourceReferenceRegistry().getResourceReference( Application.class, imageReferenceName, locale, style, variation, true, false) == null) { // Resource not available yet, so create it with factory and // share via Application final IResource imageResource = getResourceFactory(application, factoryName).newResource( specification, locale, style, variation); ResourceReference ref = new SimpleStaticResourceReference(Application.class, imageReferenceName, locale, style, variation, imageResource); application.getResourceReferenceRegistry().registerResourceReference(ref); } // Create resource reference resourceReference = new PackageResourceReference(Application.class, imageReferenceName, locale, style, variation); } else { resource = getResourceFactory(application, factoryName).newResource(specification, locale, style, variation); } } else { throw new WicketRuntimeException( "Could not generate image for value attribute '" + value + "'. Was expecting a value attribute of the form \"[resourceFactoryName]:[resourceReferenceName]?:[factorySpecification]\"."); } } /** * return the resource * * @return resource or <code>null</code> if there is none */ public final IResource getResource() { return resource; } /** * return the resource * * @return resource or <code>null</code> if there is none */ public final ResourceReference getResourceReference() { return resourceReference; } }