001: /*
002: * $Id: StringResourceModel.java 461707 2006-08-05 18:07:14Z jcompagner $
003: * $Revision: 461707 $ $Date: 2006-08-05 20:07:14 +0200 (Sat, 05 Aug 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.model;
019:
020: import java.text.MessageFormat;
021: import java.util.Locale;
022:
023: import wicket.Component;
024: import wicket.Localizer;
025: import wicket.Session;
026: import wicket.WicketRuntimeException;
027: import 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 wicket.resource.loader.ComponentStringResourceLoader}. The
056: * 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: *
092: * public MyPage extends WebPage
093: * {
094: * public MyPage(final PageParameters parameters)
095: * {
096: * add(new Label("username", new StringResourceModel("label.username", this, null)));
097: * }
098: * }
099: *
100: * </pre>
101: *
102: * Where the resource bundle for the page contains the entry
103: * <code>label.username=Username</code>
104: * <p>
105: * <b>Example 2 </b>
106: * <p>
107: * In this example, the resource key is selected based on the evaluation of a
108: * property expression:
109: *
110: * <pre>
111: *
112: * public MyPage extends WebPage
113: * {
114: * public MyPage(final PageParameters parameters)
115: * {
116: * WeatherStation ws = new WeatherStation();
117: * add(new Label("weatherMessage",
118: * new StringResourceModel("weather.${currentStatus}", this, new Model(ws)));
119: * }
120: * }
121: *
122: * </pre>
123: *
124: * Which will call the WeatherStation.getCurrentStatus() method each time the
125: * string resource model is used and where the resource bundle for the page
126: * contains the entries:
127: *
128: * <pre>
129: *
130: * weather.sunny=Don't forget sunscreen!
131: * weather.raining=You might need an umberella
132: * weather.snowing=Got your skis?
133: * weather.overcast=Best take a coat to be safe
134: *
135: * </pre>
136: *
137: * <p>
138: * <b>Example 3 </b>
139: * <p>
140: * In this example the found resource string contains a property expression that
141: * is substituted via the model:
142: *
143: * <pre>
144: *
145: *
146: *
147: *
148: *
149: * public MyPage extends WebPage
150: * {
151: * public MyPage(final PageParameters parameters)
152: * {
153: * WeatherStation ws = new WeatherStation();
154: * add(new Label("weatherMessage",
155: * new StringResourceModel("weather.message", this, new Model(ws)));
156: * }
157: * }
158: *
159: *
160: *
161: *
162: *
163: * </pre>
164: *
165: * Where the resource bundle contains the entry
166: * <code>weather.message=Weather station reports that
167: * the temperature is ${currentTemperature} ${units}</code>
168: * <p>
169: * <b>Example 4 </b>
170: * <p>
171: * In this example, the use of substitution parameters is employed to format a
172: * quite complex message string. This is an example of the most complex and
173: * powerful use of the string resource model:
174: *
175: * <pre>
176: *
177: *
178: *
179: *
180: *
181: * public MyPage extends WebPage
182: * {
183: * public MyPage(final PageParameters parameters)
184: * {
185: * WeatherStation ws = new WeatherStation();
186: * Model model = new Model(ws);
187: * add(new Label("weatherMessage",
188: * new StringResourceModel(
189: * "weather.detail", this, model,
190: * new Object[]
191: * {
192: * new Date(),
193: * new PropertyModel(model, "currentStatus"),
194: * new PropertyModel(model, "currentTemperature"),
195: * new PropertyModel(model, "units")
196: * }));
197: * }
198: * }
199: *
200: *
201: *
202: *
203: *
204: * </pre>
205: *
206: * And where the resource bundle entry is:
207: *
208: * <pre>
209: *
210: *
211: *
212: *
213: *
214: * weather.detail=The report for {0,date}, shows the temparature as {2,number,###.##} {3} \
215: * and the weather to be {1}
216: *
217: *
218: *
219: *
220: *
221: * </pre>
222: *
223: * @author Chris Turner
224: */
225: public class StringResourceModel extends
226: AbstractReadOnlyDetachableModel {
227: private static final long serialVersionUID = 1L;
228:
229: /** The locale to use. */
230: private transient Locale locale;
231:
232: /**
233: * The localizer to be used to access localized resources and the associated
234: * locale for formatting.
235: */
236: private transient Localizer localizer;
237:
238: /** The wrapped model. */
239: private IModel model;
240:
241: /** Optional parameters. */
242: private Object[] parameters;
243:
244: /** The relative component used for lookups. */
245: private Component component;
246:
247: /** The key of message to get. */
248: private String resourceKey;
249:
250: /** The default value of the message. */
251: private final String defaultValue;
252:
253: /**
254: * Construct.
255: *
256: * @param resourceKey
257: * The resource key for this string resource
258: * @param component
259: * The component that the resource is relative to
260: * @param model
261: * The model to use for property substitutions
262: * @see #StringResourceModel(String, Component, IModel, Object[])
263: */
264: public StringResourceModel(final String resourceKey,
265: final Component component, final IModel model) {
266: this (resourceKey, component, model, null, null);
267: }
268:
269: /**
270: * Construct.
271: *
272: * @param resourceKey
273: * The resource key for this string resource
274: * @param component
275: * The component that the resource is relative to
276: * @param model
277: * The model to use for property substitutions
278: * @param defaultValue
279: * The default value if the resource key is not found.
280: *
281: * @see #StringResourceModel(String, Component, IModel, Object[])
282: */
283: public StringResourceModel(final String resourceKey,
284: final Component component, final IModel model,
285: final String defaultValue) {
286: this (resourceKey, component, model, null, defaultValue);
287: }
288:
289: /**
290: * Creates a new string resource model using the supplied parameters.
291: * <p>
292: * The relative component parameter should generally be supplied, as without
293: * it resources can not be obtained from resouce bundles that are held
294: * relative to a particular component or page. However, for application that
295: * use only global resources then this parameter may be null.
296: * <p>
297: * The model parameter is also optional and only needs to be supplied if
298: * value substitutions are to take place on either the resource key or the
299: * actual resource strings.
300: * <p>
301: * The parameters parameter is also optional and is used for substitutions.
302: *
303: * @param resourceKey
304: * The resource key for this string resource
305: * @param component
306: * The component that the resource is relative to
307: * @param model
308: * The model to use for property substitutions
309: * @param parameters
310: * The parameters to substitute using a Java MessageFormat object
311: */
312: public StringResourceModel(final String resourceKey,
313: final Component component, final IModel model,
314: final Object[] parameters) {
315: this (resourceKey, component, model, parameters, null);
316: }
317:
318: /**
319: * Creates a new string resource model using the supplied parameters.
320: * <p>
321: * The relative component parameter should generally be supplied, as without
322: * it resources can not be obtained from resouce bundles that are held
323: * relative to a particular component or page. However, for application that
324: * use only global resources then this parameter may be null.
325: * <p>
326: * The model parameter is also optional and only needs to be supplied if
327: * value substitutions are to take place on either the resource key or the
328: * actual resource strings.
329: * <p>
330: * The parameters parameter is also optional and is used for substitutions.
331: *
332: * @param resourceKey
333: * The resource key for this string resource
334: * @param component
335: * The component that the resource is relative to
336: * @param model
337: * The model to use for property substitutions
338: * @param parameters
339: * The parameters to substitute using a Java MessageFormat object
340: * @param defaultValue
341: * The default value if the resource key is not found.
342: */
343: public StringResourceModel(final String resourceKey,
344: final Component component, final IModel model,
345: final Object[] parameters, final String defaultValue) {
346: if (resourceKey == null) {
347: throw new IllegalArgumentException(
348: "Resource key must not be null");
349: }
350: this .resourceKey = resourceKey;
351: this .component = component;
352: this .model = model;
353: this .parameters = parameters;
354: this .defaultValue = defaultValue;
355: }
356:
357: /**
358: * Gets the localizer that is being used by this string resource model.
359: *
360: * @return The localizer
361: */
362: public Localizer getLocalizer() {
363: return localizer;
364: }
365:
366: /**
367: * Gets the model used for property substitutions.
368: *
369: * @return The model
370: */
371: public final IModel getNestedModel() {
372: return model;
373: }
374:
375: /**
376: * Gets the string currently represented by this string resource model. The
377: * string that is returned may vary for each call to this method depending
378: * on the values contained in the model and an the parameters that were
379: * passed when this string resource model was created.
380: *
381: * @return The string
382: */
383: public final String getString() {
384: // Make sure we have a localizer before commencing
385: if (getLocalizer() == null) {
386: if (component != null) {
387: setLocalizer(component.getLocalizer());
388: } else {
389: throw new IllegalStateException(
390: "No localizer has been set");
391: }
392: }
393:
394: // Get the string resource, doing any property substitutions as part
395: // of the get operation
396: String s = localizer.getString(getResourceKey(), component,
397: model);
398: if (s == null)
399: s = defaultValue;
400:
401: if (s != null) {
402: // Substitute any parameters if necessary
403: Object[] parameters = getParameters();
404: if (parameters != null) {
405: // Build the real parameters
406: Object[] realParams = new Object[parameters.length];
407: for (int i = 0; i < parameters.length; i++) {
408: if (parameters[i] instanceof IModel) {
409: realParams[i] = ((IModel) parameters[i])
410: .getObject(component);
411: } else if (model != null
412: && parameters[i] instanceof String) {
413: realParams[i] = PropertyVariableInterpolator
414: .interpolate((String) parameters[i],
415: model.getObject(component));
416: } else {
417: realParams[i] = parameters[i];
418: }
419: }
420:
421: // Apply the parameters
422: final MessageFormat format = new MessageFormat(s,
423: component != null ? component.getLocale()
424: : locale);
425: s = format.format(realParams);
426: }
427: }
428:
429: // Return the string resource
430: return s;
431: }
432:
433: /**
434: * Sets the localizer that is being used by this string resource model. This
435: * method is provided to allow the default application localizer to be
436: * overridden if required.
437: *
438: * @param localizer
439: * The localizer to use
440: */
441: public void setLocalizer(final Localizer localizer) {
442: this .localizer = localizer;
443: }
444:
445: /**
446: * Override of the default method to return the resource string represented
447: * by this string resource model. Useful in debugging and so on, to avoid
448: * the explicit need to call the getString() method.
449: *
450: * @return The string for this model object
451: */
452: public String toString() {
453: return getString();
454: }
455:
456: /**
457: * Gets the Java MessageFormat substitution parameters.
458: *
459: * @return The substitution parameters
460: */
461: protected Object[] getParameters() {
462: return parameters;
463: }
464:
465: /**
466: * Gets the resource key for this string resource. If the resource key
467: * contains property expressions and the model is null then the returned
468: * value is the actual resource key with all substitutions undertaken.
469: *
470: * @return The (possibly substituted) resource key
471: */
472: protected final String getResourceKey() {
473: if (model != null) {
474: return PropertyVariableInterpolator.interpolate(
475: resourceKey, model.getObject(component));
476: } else {
477: return resourceKey;
478: }
479: }
480:
481: /**
482: * Attaches to the given session.
483: */
484: protected final void onAttach() {
485: // Initialise information that we need to work successfully
486: final Session session = Session.get();
487: if (session != null) {
488: this .localizer = session.getApplication()
489: .getResourceSettings().getLocalizer();
490: this .locale = session.getLocale();
491: } else {
492: throw new WicketRuntimeException(
493: "Cannot attach a string resource model without a Session context because that is required to get a Localizer");
494: }
495: }
496:
497: /**
498: * Detaches from the given session
499: */
500: protected final void onDetach() {
501: // Detach any model
502: if (model != null) {
503: model.detach();
504: }
505:
506: // Null out references
507: this .localizer = null;
508: this .locale = null;
509: }
510:
511: /**
512: * Gets the string that this string resource model currently represents. The
513: * string is returned as an object to allow it to be used generically within
514: * components.
515: *
516: * @see AbstractDetachableModel#onGetObject(Component)
517: */
518: protected final Object onGetObject(final Component component) {
519: if (this.component == null) {
520: this.component = component;
521: }
522: return getString();
523: }
524: }
|