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: */
017: package org.apache.wicket.model;
018:
019: import java.text.MessageFormat;
020: import java.util.Locale;
021:
022: import org.apache.wicket.Application;
023: import org.apache.wicket.Component;
024: import org.apache.wicket.Localizer;
025: import org.apache.wicket.Session;
026: import org.apache.wicket.WicketRuntimeException;
027: import org.apache.wicket.util.string.interpolator.PropertyVariableInterpolator;
028:
029: /**
030: * This model class encapsulates the full power of localization support within
031: * the Wicket framework. It combines the flexible Wicket resource loading
032: * mechanism with property expressions, property models and standard Java
033: * <code>MessageFormat</code> substitutions. This combination should be able
034: * to solve any dynamic localization requirement that a project has.
035: * <p>
036: * The model should be created with four parameters, which are described in
037: * detail below:
038: * <ul>
039: * <li><b>resourceKey </b>- This is the most important parameter as it contains
040: * the key that should be used to obtain resources from any string resource
041: * loaders. This paramater is mandatory: a null value will throw an exception.
042: * Typically it will contain an ordinary string such as
043: * "label.username". To add extra power to the key functionality the
044: * key may also contain a property expression which will be evaluated if the
045: * model parameter (see below) is not null. This allows keys to be changed
046: * dynamically as the application is running. For example, the key could be
047: * "product.${product.id}" which prior to rendering will call
048: * model.getObject().getProduct().getId() and substitute this value into the
049: * resource key before is is passed to the loader.
050: * <li><b>component </b>- This parameter should be a component that the string
051: * resource is relative to. In a simple application this will usually be the
052: * Page on which the component resides. For reusable components/containers that
053: * are packaged with their own string resource bundles it should be the actual
054: * component/container rather than the page. For more information on this please
055: * see {@link org.apache.wicket.resource.loader.ComponentStringResourceLoader}.
056: * The relative component may actually be <code>null</code> when all resource
057: * loading is to be done from a global resource loader. However, we recommend
058: * that a relative component is still supplied even in these cases in order to
059: * 'future proof' your application with regards to changing resource loading
060: * strategies.
061: * <li><b>model </b>- This parameter is mandatory if either the resourceKey,
062: * the found string resource (see below) or any of the substitution parameters
063: * (see below) contain property expressions. Where property expressions are
064: * present they will all be evaluated relative to this model object. If there
065: * are no property expressions present then this model parameter may be
066: * <code>null</code>
067: * <li><b>parameters </b>- The parameters parameter allows an array of objects
068: * to be passed for substitution on the found string resource (see below) using
069: * a standard <code>java.text.MessageFormat</code> object. Each parameter may
070: * be an ordinary Object, in which case it will be processed by the standard
071: * formatting rules associated with <code>java.text.MessageFormat</code>.
072: * Alternatively, the parameter may be an instance of <code>IModel</code> in
073: * which case the <code>getObject()</code> method will be applied prior to the
074: * parameter being passed to the <code>java.text.MessageFormat</code>. This
075: * allows such features dynamic parameters that are obtained using a
076: * <code>PropertyModel</code> object or even nested string resource models.
077: * </ul>
078: * As well as the supplied parameters, the found string resource can contain
079: * formatting information. It may contain property expressions in which case
080: * these are evaluated using the model object supplied when the string resource
081: * model is created. The string resource may also contain
082: * <code>java.text.MessageFormat</code> style markup for replacement of
083: * parameters. Where a string resource contains both types of formatting
084: * information then the property expression will be applied first.
085: * <p>
086: * <b>Example 1 </b>
087: * <p>
088: * In its simplest form, the model can be used as follows:
089: *
090: * <pre>
091: * public MyPage extends WebPage
092: * {
093: * public MyPage(final PageParameters parameters)
094: * {
095: * add(new Label("username", new StringResourceModel("label.username", this, null)));
096: * }
097: * }
098: * </pre>
099: *
100: * Where the resource bundle for the page contains the entry
101: * <code>label.username=Username</code>
102: * <p>
103: * <b>Example 2 </b>
104: * <p>
105: * In this example, the resource key is selected based on the evaluation of a
106: * property expression:
107: *
108: * <pre>
109: * public MyPage extends WebPage
110: * {
111: * public MyPage(final PageParameters parameters)
112: * {
113: * WeatherStation ws = new WeatherStation();
114: * add(new Label("weatherMessage",
115: * new StringResourceModel("weather.${currentStatus}", this, new Model(ws)));
116: * }
117: * }
118: * </pre>
119: *
120: * Which will call the WeatherStation.getCurrentStatus() method each time the
121: * string resource model is used and where the resource bundle for the page
122: * contains the entries:
123: *
124: * <pre>
125: * weather.sunny=Don't forget sunscreen!
126: * weather.raining=You might need an umberella
127: * weather.snowing=Got your skis?
128: * weather.overcast=Best take a coat to be safe
129: * </pre>
130: *
131: * <p>
132: * <b>Example 3 </b>
133: * <p>
134: * In this example the found resource string contains a property expression that
135: * is substituted via the model:
136: *
137: * <pre>
138: * public MyPage extends WebPage
139: * {
140: * public MyPage(final PageParameters parameters)
141: * {
142: * WeatherStation ws = new WeatherStation();
143: * add(new Label("weatherMessage",
144: * new StringResourceModel("weather.message", this, new Model(ws)));
145: * }
146: * }
147: * </pre>
148: *
149: * Where the resource bundle contains the entry
150: * <code>weather.message=Weather station reports that
151: * the temperature is ${currentTemperature} ${units}</code>
152: * <p>
153: * <b>Example 4 </b>
154: * <p>
155: * In this example, the use of substitution parameters is employed to format a
156: * quite complex message string. This is an example of the most complex and
157: * powerful use of the string resource model:
158: *
159: * <pre>
160: * public MyPage extends WebPage
161: * {
162: * public MyPage(final PageParameters parameters)
163: * {
164: * WeatherStation ws = new WeatherStation();
165: * Model model = new Model(ws);
166: * add(new Label("weatherMessage",
167: * new StringResourceModel(
168: * "weather.detail", this, model,
169: * new Object[]
170: * {
171: * new Date(),
172: * new PropertyModel(model, "currentStatus"),
173: * new PropertyModel(model, "currentTemperature"),
174: * new PropertyModel(model, "units")
175: * }));
176: * }
177: * }
178: * </pre>
179: *
180: * And where the resource bundle entry is:
181: *
182: * <pre>
183: * weather.detail=The report for {0,date}, shows the temperature as {2,number,###.##} {3} \
184: * and the weather to be {1}
185: * </pre>
186: *
187: * @author Chris Turner
188: */
189: public class StringResourceModel extends LoadableDetachableModel {
190: private static final long serialVersionUID = 1L;
191:
192: /** The locale to use. */
193: private transient Locale locale;
194:
195: /**
196: * The localizer to be used to access localized resources and the associated
197: * locale for formatting.
198: */
199: private transient Localizer localizer;
200:
201: /** The wrapped model. */
202: private final IModel model;
203:
204: /** Optional parameters. */
205: private final Object[] parameters;
206:
207: /** The relative component used for lookups. */
208: private final Component component;
209:
210: /** The key of message to get. */
211: private final String resourceKey;
212:
213: /** The default value of the message. */
214: private final String defaultValue;
215:
216: /**
217: * Construct.
218: *
219: * @param resourceKey
220: * The resource key for this string resource
221: * @param component
222: * The component that the resource is relative to
223: * @param model
224: * The model to use for property substitutions
225: * @see #StringResourceModel(String, Component, IModel, Object[])
226: */
227: public StringResourceModel(final String resourceKey,
228: final Component component, final IModel model) {
229: this (resourceKey, component, model, null, null);
230: }
231:
232: /**
233: * Construct.
234: *
235: * @param resourceKey
236: * The resource key for this string resource
237: * @param component
238: * The component that the resource is relative to
239: * @param model
240: * The model to use for property substitutions
241: * @param defaultValue
242: * The default value if the resource key is not found.
243: *
244: * @see #StringResourceModel(String, Component, IModel, Object[])
245: */
246: public StringResourceModel(final String resourceKey,
247: final Component component, final IModel model,
248: final String defaultValue) {
249: this (resourceKey, component, model, null, defaultValue);
250: }
251:
252: /**
253: * Creates a new string resource model using the supplied parameters.
254: * <p>
255: * The relative component parameter should generally be supplied, as without
256: * it resources can not be obtained from resouce bundles that are held
257: * relative to a particular component or page. However, for application that
258: * use only global resources then this parameter may be null.
259: * <p>
260: * The model parameter is also optional and only needs to be supplied if
261: * value substitutions are to take place on either the resource key or the
262: * actual resource strings.
263: * <p>
264: * The parameters parameter is also optional and is used for substitutions.
265: *
266: * @param resourceKey
267: * The resource key for this string resource
268: * @param component
269: * The component that the resource is relative to
270: * @param model
271: * The model to use for property substitutions
272: * @param parameters
273: * The parameters to substitute using a Java MessageFormat object
274: */
275: public StringResourceModel(final String resourceKey,
276: final Component component, final IModel model,
277: final Object[] parameters) {
278: this (resourceKey, component, model, parameters, null);
279: }
280:
281: /**
282: * Creates a new string resource model using the supplied parameters.
283: * <p>
284: * The relative component parameter should generally be supplied, as without
285: * it resources can not be obtained from resouce bundles that are held
286: * relative to a particular component or page. However, for application that
287: * use only global resources then this parameter may be null.
288: * <p>
289: * The model parameter is also optional and only needs to be supplied if
290: * value substitutions are to take place on either the resource key or the
291: * actual resource strings.
292: * <p>
293: * The parameters parameter is also optional and is used for substitutions.
294: *
295: * @param resourceKey
296: * The resource key for this string resource
297: * @param component
298: * The component that the resource is relative to
299: * @param model
300: * The model to use for property substitutions
301: * @param parameters
302: * The parameters to substitute using a Java MessageFormat object
303: * @param defaultValue
304: * The default value if the resource key is not found.
305: */
306: public StringResourceModel(final String resourceKey,
307: final Component component, final IModel model,
308: final Object[] parameters, final String defaultValue) {
309: if (resourceKey == null) {
310: throw new IllegalArgumentException(
311: "Resource key must not be null");
312: }
313: this .resourceKey = resourceKey;
314: this .component = component;
315: this .model = model;
316: this .parameters = parameters;
317: this .defaultValue = defaultValue;
318: }
319:
320: /**
321: * Gets the localizer that is being used by this string resource model.
322: *
323: * @return The localizer
324: */
325: public Localizer getLocalizer() {
326: return localizer;
327: }
328:
329: /**
330: * Gets the string currently represented by this string resource model. The
331: * string that is returned may vary for each call to this method depending
332: * on the values contained in the model and an the parameters that were
333: * passed when this string resource model was created.
334: *
335: * @return The string
336: */
337: public final String getString() {
338: // Make sure we have a localizer before commencing
339: if (getLocalizer() == null) {
340: if (component != null) {
341: setLocalizer(component.getLocalizer());
342: } else {
343: throw new IllegalStateException(
344: "No localizer has been set");
345: }
346: }
347:
348: // Get the string resource, doing any property substitutions as part
349: // of the get operation
350: String value = localizer.getString(getResourceKey(), component,
351: model, defaultValue);
352: if (value == null) {
353: value = defaultValue;
354: }
355:
356: if (value != null) {
357: // Substitute any parameters if necessary
358: Object[] parameters = getParameters();
359: if (parameters != null) {
360: // Build the real parameters
361: Object[] realParams = new Object[parameters.length];
362: for (int i = 0; i < parameters.length; i++) {
363: if (parameters[i] instanceof IModel) {
364: realParams[i] = ((IModel) parameters[i])
365: .getObject();
366: } else if (model != null
367: && parameters[i] instanceof String) {
368: realParams[i] = PropertyVariableInterpolator
369: .interpolate((String) parameters[i],
370: model.getObject());
371: } else {
372: realParams[i] = parameters[i];
373: }
374: }
375:
376: // Apply the parameters
377: final MessageFormat format = new MessageFormat(value,
378: component != null ? component.getLocale()
379: : locale);
380: value = format.format(realParams);
381: }
382: }
383:
384: // Return the string resource
385: return value;
386: }
387:
388: /**
389: * Sets the localizer that is being used by this string resource model. This
390: * method is provided to allow the default application localizer to be
391: * overridden if required.
392: *
393: * @param localizer
394: * The localizer to use
395: */
396: public void setLocalizer(final Localizer localizer) {
397: this .localizer = localizer;
398: }
399:
400: /**
401: * Override of the default method to return the resource string represented
402: * by this string resource model. Useful in debugging and so on, to avoid
403: * the explicit need to call the getString() method.
404: *
405: * @return The string for this model object
406: */
407: public String toString() {
408: return getString();
409: }
410:
411: /**
412: * Gets the Java MessageFormat substitution parameters.
413: *
414: * @return The substitution parameters
415: */
416: protected Object[] getParameters() {
417: return parameters;
418: }
419:
420: /**
421: * Gets the resource key for this string resource. If the resource key
422: * contains property expressions and the model is null then the returned
423: * value is the actual resource key with all substitutions undertaken.
424: *
425: * @return The (possibly substituted) resource key
426: */
427: protected final String getResourceKey() {
428: if (model != null) {
429: return PropertyVariableInterpolator.interpolate(
430: resourceKey, model.getObject());
431: } else {
432: return resourceKey;
433: }
434: }
435:
436: /**
437: * Gets the string that this string resource model currently represents. The
438: * string is returned as an object to allow it to be used generically within
439: * components.
440: *
441: */
442: protected Object load() {
443: // Initialise information that we need to work successfully
444: final Session session = Session.get();
445: if (session != null) {
446: this .localizer = Application.get().getResourceSettings()
447: .getLocalizer();
448: this .locale = session.getLocale();
449: } else {
450: throw new WicketRuntimeException(
451: "Cannot attach a string resource model without a Session context because that is required to get a Localizer");
452: }
453: return getString();
454: }
455:
456: /**
457: * Detaches from the given session
458: */
459: protected final void onDetach() {
460: // Detach any model
461: if (model != null) {
462: model.detach();
463: }
464:
465: // Null out references
466: this.localizer = null;
467: this.locale = null;
468: }
469: }
|