001: /*
002: * $Id: LocalizedImageResource.java 460602 2006-05-13 15:36:17Z jcompagner $
003: * $Revision: 460602 $ $Date: 2006-05-13 17:36:17 +0200 (Sat, 13 May 2006) $
004: *
005: * ==============================================================================
006: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
007: * use this file except in compliance with the License. You may obtain a copy of
008: * the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
014: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
015: * License for the specific language governing permissions and limitations under
016: * the License.
017: */
018: package wicket.markup.html.image.resource;
019:
020: import java.io.Serializable;
021: import java.util.Locale;
022:
023: import wicket.Application;
024: import wicket.Component;
025: import wicket.IResourceFactory;
026: import wicket.IResourceListener;
027: import wicket.MarkupContainer;
028: import wicket.RequestCycle;
029: import wicket.Resource;
030: import wicket.ResourceReference;
031: import wicket.WicketRuntimeException;
032: import wicket.markup.ComponentTag;
033: import wicket.markup.html.PackageResource;
034: import wicket.markup.html.border.Border;
035: import wicket.util.lang.Objects;
036: import wicket.util.parse.metapattern.Group;
037: import wicket.util.parse.metapattern.MetaPattern;
038: import wicket.util.parse.metapattern.OptionalMetaPattern;
039: import wicket.util.parse.metapattern.parsers.MetaPatternParser;
040: import wicket.util.string.Strings;
041: import wicket.util.value.ValueMap;
042:
043: /**
044: * THIS CLASS IS INTENDED FOR INTERNAL USE IN IMPLEMENTING LOCALE SENSITIVE
045: * COMPONENTS THAT USE IMAGE RESOURCES AND SHOULD NOT BE USED DIRECTLY BY
046: * END-USERS.
047: * <p>
048: * This class contains the logic for extracting static image resources
049: * referenced by the SRC attribute of component tags and keeping these static
050: * image resources in sync with the component locale.
051: * <p>
052: * If no image is specified by the SRC attribute of an IMG tag, then any VALUE
053: * attribute is inspected. If there is a VALUE attribute, it must be of the form
054: * "[factoryName]:[sharedImageName]?:[specification]". [factoryName] is the name
055: * of a resource factory that has been added to Application (for example,
056: * DefaultButtonImageResourceFactory is installed by default under the name
057: * "buttonFactory"). The [sharedImageName] value is optional and gives a name
058: * under which a given generated image is shared. For example, a cancel button
059: * image generated by the VALUE attribute "buttonFactory:cancelButton:Cancel" is
060: * shared under the name "cancelButton" and this specification will cause a
061: * component to reference the same image resource no matter what page it appears
062: * on, which is a very convenient and efficient way to create and share images.
063: * The [specification] string which follows the second colon is passed directly
064: * to the image factory and its format is dependent on the specific image
065: * factory. For details on the default buttonFactory, see
066: * {@link wicket.markup.html.image.resource.DefaultButtonImageResourceFactory}.
067: * <p>
068: * Finally, if there is no SRC attribute and no VALUE attribute, the Image
069: * component's model is converted to a String and that value is used as a path
070: * to load the image.
071: *
072: * @author Jonathan Locke
073: */
074: public final class LocalizedImageResource implements Serializable,
075: IResourceListener {
076: private static final long serialVersionUID = 1L;
077:
078: /** What kind of resource it is. TRUE==Resource is set, FALSE==ResourceReference is set, null none */
079: private Boolean resourceKind;
080:
081: /** The component that is referencing this image resource */
082: private Component component;
083:
084: /** The image resource this image component references */
085: private Resource resource;
086:
087: /** The resource reference */
088: private ResourceReference resourceReference;
089:
090: /** The resource parameters */
091: private ValueMap resourceParameters;
092:
093: /** The locale of the image resource */
094: private transient Locale locale;
095:
096: /** The style of the image resource */
097: private transient String style;
098:
099: /**
100: * Parses image value specifications of the form "[factoryName]:
101: * [shared-image-name]?:[specification]"
102: *
103: * @author Jonathan Locke
104: */
105: private static final class ImageValueParser extends
106: MetaPatternParser {
107: /** Factory name */
108: private static final Group factoryName = new Group(
109: MetaPattern.VARIABLE_NAME);
110:
111: /** Image reference name */
112: private static final Group imageReferenceName = new Group(
113: MetaPattern.VARIABLE_NAME);
114:
115: /** Factory specification string */
116: private static final Group specification = new Group(
117: MetaPattern.ANYTHING_NON_EMPTY);
118:
119: /** Meta pattern. */
120: private static final MetaPattern pattern = new MetaPattern(
121: new MetaPattern[] {
122: factoryName,
123: MetaPattern.COLON,
124: new OptionalMetaPattern(
125: new MetaPattern[] { imageReferenceName }),
126: MetaPattern.COLON, specification });
127:
128: /**
129: * Construct.
130: *
131: * @param input
132: * to parse
133: */
134: private ImageValueParser(final CharSequence input) {
135: super (pattern, input);
136: }
137:
138: /**
139: * @return The factory name
140: */
141: private String getFactoryName() {
142: return factoryName.get(matcher());
143: }
144:
145: /**
146: * @return Returns the imageReferenceName.
147: */
148: private String getImageReferenceName() {
149: return imageReferenceName.get(matcher());
150: }
151:
152: /**
153: * @return Returns the specification.
154: */
155: private String getSpecification() {
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: this .component = component;
168: this .locale = component.getLocale();
169: this .style = component.getStyle();
170: }
171:
172: /**
173: * Binds this resource if it is shared
174: */
175: public final void bind() {
176: // If we have a resource reference
177: if (resourceReference != null) {
178: // Bind the reference to the application
179: resourceReference.bind(component.getApplication());
180:
181: // Then dereference the resource
182: resource = resourceReference.getResource();
183: }
184: }
185:
186: /**
187: * @see wicket.IResourceListener#onResourceRequested()
188: */
189: public final void onResourceRequested() {
190: bind();
191: resource.onResourceRequested();
192: }
193:
194: /**
195: * @param resource
196: * The resource to set.
197: */
198: public final void setResource(final Resource resource) {
199: resourceKind = Boolean.TRUE;
200: this .resource = resource;
201: }
202:
203: /**
204: * @param resourceReference
205: * The resource to set.
206: */
207: public final void setResourceReference(
208: final ResourceReference resourceReference) {
209: setResourceReference(resourceReference, null);
210: }
211:
212: /**
213: * @param resourceReference
214: * The resource to set.
215: * @param resourceParameters
216: * The resource parameters for the shared resource
217: */
218: public final void setResourceReference(
219: final ResourceReference resourceReference,
220: final ValueMap resourceParameters) {
221: resourceKind = Boolean.FALSE;
222: this .resourceReference = resourceReference;
223: this .resourceParameters = resourceParameters;
224: bind();
225: }
226:
227: /**
228: * @param tag
229: * The tag to inspect for an optional src attribute that might
230: * reference an image.
231: * @throws WicketRuntimeException
232: * Thrown if an image is required by the caller, but none can be
233: * found.
234: */
235: public final void setSrcAttribute(final ComponentTag tag) {
236: // If locale has changed from the initial locale used to attach image
237: // resource, then we need to reload the resource in the new locale
238: if (resourceKind == null
239: && (!Objects.equal(locale, component.getLocale()) || !Objects
240: .equal(style, component.getStyle()))) {
241: // Get new component locale and style
242: this .locale = component.getLocale();
243: this .style = component.getStyle();
244:
245: // Invalidate current resource so it will be reloaded/recomputed
246: this .resourceReference = null;
247: this .resource = null;
248: } else {
249: // TODO post 1.2: should we have support for locale changes when the
250: // resource reference (or resource??) is set manually..
251: // We should get a new resource reference for the current locale then
252: // that points to the same resource but with another locale if it exists.
253: // something like SharedResource.getResourceReferenceForLocale(resourceReference);
254: }
255:
256: // Need to load image resource for this component?
257: if (resource == null && resourceReference == null) {
258: // Get SRC attribute of tag
259: final CharSequence src = tag.getString("src");
260: if (src != null) {
261: // Try to load static image
262: loadStaticImage(src.toString());
263: } else {
264: // Get VALUE attribute of tag
265: final CharSequence value = tag.getString("value");
266: if (value != null) {
267: // Try to generate an image using an image factory
268: newImage(value);
269: } else {
270: // Load static image using model object as the path
271: loadStaticImage(component.getModelObjectAsString());
272: }
273: }
274: }
275:
276: // Get URL for resource
277: final CharSequence url;
278: if (this .resourceReference != null) {
279: // Create URL to shared resource
280: url = RequestCycle.get().urlFor(resourceReference,
281: resourceParameters);
282: } else {
283: // Create URL to component
284: url = component.urlFor(IResourceListener.INTERFACE);
285: }
286:
287: // Set the SRC attribute to point to the component or shared resource
288: tag.put("src", Strings.replaceAll(RequestCycle.get()
289: .getOriginalResponse().encodeURL(url), "&", "&"));
290: }
291:
292: /**
293: * @param application
294: * The application
295: * @param factoryName
296: * The name of the image resource factory
297: * @return The resource factory
298: * @throws WicketRuntimeException
299: * Thrown if factory cannot be found
300: */
301: private IResourceFactory getResourceFactory(
302: final Application application, final String factoryName) {
303: final IResourceFactory factory = application
304: .getResourceSettings().getResourceFactory(factoryName);
305:
306: // Found factory?
307: if (factory == null) {
308: throw new WicketRuntimeException(
309: "Could not find image resource factory named "
310: + factoryName);
311: }
312: return factory;
313: }
314:
315: /**
316: * Tries to load static image at the given path and throws an exception if
317: * the image cannot be located.
318: *
319: * @param path
320: * The path to the image
321: * @throws WicketRuntimeException
322: * Thrown if the image cannot be located
323: */
324: private void loadStaticImage(final String path) {
325: if ((path.indexOf("..") != -1) || (path.indexOf("./") != -1)
326: || (path.indexOf("/.") != -1)) {
327: throw new WicketRuntimeException(
328: "The 'src' attribute must not contain any of the following strings: '..', './', '/.': path="
329: + path);
330: }
331:
332: MarkupContainer parent = component
333: .findParentWithAssociatedMarkup();
334: if (parent instanceof Border) {
335: parent = parent.getParent();
336: }
337: final Class scope = parent.getClass();
338: this .resourceReference = new ResourceReference(scope, path) {
339: private static final long serialVersionUID = 1L;
340:
341: /**
342: * @see wicket.ResourceReference#newResource()
343: */
344: protected Resource newResource() {
345: PackageResource pr = PackageResource.get(getScope(),
346: getName(), LocalizedImageResource.this .locale,
347: style);
348: locale = pr.getLocale();
349: return pr;
350: }
351: };
352: resourceReference.setLocale(locale);
353: resourceReference.setStyle(style);
354: bind();
355: }
356:
357: /**
358: * Generates an image resource based on the attribute values on tag
359: *
360: * @param value
361: * The value to parse
362: */
363: private void newImage(final CharSequence value) {
364: // Parse value
365: final ImageValueParser valueParser = new ImageValueParser(value);
366:
367: // Does value match parser?
368: if (valueParser.matches()) {
369: final String imageReferenceName = valueParser
370: .getImageReferenceName();
371: final String specification = Strings
372: .replaceHtmlEscapeNumber(valueParser
373: .getSpecification());
374: final String factoryName = valueParser.getFactoryName();
375: final Application application = component.getApplication();
376:
377: // Do we have a reference?
378: if (!Strings.isEmpty(imageReferenceName)) {
379: // Is resource already available via the application?
380: if (application.getSharedResources().get(
381: Application.class, imageReferenceName, locale,
382: style, true) == null) {
383: // Resource not available yet, so create it with factory and
384: // share via Application
385: final Resource imageResource = getResourceFactory(
386: application, factoryName).newResource(
387: specification, locale, style);
388: application.getSharedResources().add(
389: Application.class, imageReferenceName,
390: locale, style, imageResource);
391: }
392:
393: // Create resource reference
394: this .resourceReference = new ResourceReference(
395: Application.class, imageReferenceName);
396: resourceReference.setLocale(locale);
397: resourceReference.setStyle(style);
398: } else {
399: this .resource = getResourceFactory(application,
400: factoryName).newResource(specification, locale,
401: style);
402: }
403: } else {
404: throw new WicketRuntimeException(
405: "Could not generate image for value attribute '"
406: + value
407: + "'. Was expecting a value attribute of the form \"[resourceFactoryName]:[resourceReferenceName]?:[factorySpecification]\".");
408: }
409: }
410: }
|