001: /*
002: * $Id: Localizer.java 461011 2006-06-12 03:41:44Z ehillenius $
003: * $Revision: 461011 $ $Date: 2006-06-12 05:41:44 +0200 (Mon, 12 Jun 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;
019:
020: import java.util.ArrayList;
021: import java.util.Iterator;
022: import java.util.List;
023: import java.util.Locale;
024: import java.util.MissingResourceException;
025:
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028:
029: import wicket.model.IModel;
030: import wicket.resource.loader.IStringResourceLoader;
031: import wicket.settings.IResourceSettings;
032: import wicket.util.string.AppendingStringBuffer;
033: import wicket.util.string.Strings;
034: import wicket.util.string.interpolator.PropertyVariableInterpolator;
035:
036: /**
037: * A utility class that encapsulates all of the localization related
038: * functionality in a way that it can be accessed by all areas of the framework
039: * in a consistent way. A singleton instance of this class is available via the
040: * <code>Application</code> object.
041: * <p>
042: * You may register additional IStringResourceLoader to extend or replace
043: * Wickets default search strategy for the properties. E.g. string resource
044: * loaders which load the properties from a database. There should be no need to
045: * extend Localizer.
046: *
047: * @see wicket.settings.Settings#getLocalizer()
048: * @see wicket.resource.loader.IStringResourceLoader
049: * @see wicket.settings.Settings#getStringResourceLoaders()
050: *
051: * @author Chris Turner
052: * @author Juergen Donnerstag
053: */
054: public class Localizer {
055: /** Log */
056: private static final Log log = LogFactory.getLog(Localizer.class);
057:
058: /** The application and its settings to use to control the utils. */
059: private Application application;
060:
061: /**
062: * Create the utils instance class backed by the configuration information
063: * contained within the supplied application object.
064: *
065: * @param application
066: * The application to localize for
067: */
068: public Localizer(final Application application) {
069: this .application = application;
070: }
071:
072: /**
073: * @see #getString(String, Component, IModel, Locale, String, String)
074: *
075: * @param key
076: * The key to obtain the resource for
077: * @param component
078: * The component to get the resource for
079: * @return The string resource
080: * @throws MissingResourceException
081: * If resource not found and configuration dictates that
082: * exception should be thrown
083: */
084: public String getString(final String key, final Component component)
085: throws MissingResourceException {
086: if (component != null) {
087: return getString(key, component, null, component
088: .getLocale(), component.getStyle(), null);
089: } else {
090: Session session = Session.get();
091: return getString(key, component, null, session.getLocale(),
092: session.getStyle(), null);
093: }
094: }
095:
096: /**
097: * @see #getString(String, Component, IModel, Locale, String, String)
098: *
099: * @param key
100: * The key to obtain the resource for
101: * @param component
102: * The component to get the resource for
103: * @param model
104: * The model to use for property substitutions in the strings
105: * (optional)
106: * @return The string resource
107: * @throws MissingResourceException
108: * If resource not found and configuration dictates that
109: * exception should be thrown
110: */
111: public String getString(final String key,
112: final Component component, final IModel model)
113: throws MissingResourceException {
114: if (component != null) {
115: return getString(key, component, model, component
116: .getLocale(), component.getStyle(), null);
117: } else {
118: Session session = Session.get();
119: return getString(key, component, model,
120: session.getLocale(), session.getStyle(), null);
121: }
122: }
123:
124: /**
125: * @see #getString(String, Component, IModel, Locale, String, String)
126: *
127: * @param key
128: * The key to obtain the resource for
129: * @param component
130: * The component to get the resource for
131: * @param model
132: * The model to use for property substitutions in the strings
133: * (optional)
134: * @param defaultValue
135: * The default value (optional)
136: * @return The string resource
137: * @throws MissingResourceException
138: * If resource not found and configuration dictates that
139: * exception should be thrown
140: */
141: public String getString(final String key,
142: final Component component, final IModel model,
143: final String defaultValue) throws MissingResourceException {
144: if (component != null) {
145: return getString(key, component, model, component
146: .getLocale(), component.getStyle(), defaultValue);
147: } else {
148: Session session = Session.get();
149: return getString(key, component, model,
150: session.getLocale(), session.getStyle(),
151: defaultValue);
152: }
153: }
154:
155: /**
156: * @see #getString(String, Component, IModel, Locale, String, String)
157: *
158: * @param key
159: * The key to obtain the resource for
160: * @param component
161: * The component to get the resource for
162: * @param defaultValue
163: * The default value (optional)
164: * @return The string resource
165: * @throws MissingResourceException
166: * If resource not found and configuration dictates that
167: * exception should be thrown
168: */
169: public String getString(final String key,
170: final Component component, final String defaultValue)
171: throws MissingResourceException {
172: if (component != null) {
173: return getString(key, component, null, component
174: .getLocale(), component.getStyle(), defaultValue);
175: } else {
176: Session session = Session.get();
177: return getString(key, component, null, session.getLocale(),
178: session.getStyle(), defaultValue);
179: }
180: }
181:
182: /**
183: * Get the localized string using all of the supplied parameters. This
184: * method is left public to allow developers full control over string
185: * resource loading. However, it is recommended that one of the other
186: * convenience methods in the class are used as they handle all of the work
187: * related to obtaining the current user locale and style information.
188: *
189: * @param key
190: * The key to obtain the resource for
191: * @param component
192: * The component to get the resource for (optional)
193: * @param model
194: * The model to use for substitutions in the strings (optional)
195: * @param locale
196: * The locale to get the resource for (optional)
197: * @param style
198: * The style to get the resource for (optional) (see
199: * {@link wicket.Session})
200: * @param defaultValue
201: * The default value (optional)
202: * @return The string resource
203: * @throws MissingResourceException
204: * If resource not found and configuration dictates that
205: * exception should be thrown
206: */
207: public String getString(final String key,
208: final Component component, final IModel model,
209: final Locale locale, final String style,
210: final String defaultValue) throws MissingResourceException {
211: final List searchStack;
212: final String path;
213: if (component != null) {
214: // The reason why need to create that stack is because we need to
215: // walk it downwards starting with Page down to the Component
216: searchStack = getComponentStack(component);
217: path = Strings.replaceAll(component.getPageRelativePath(),
218: ":", ".").toString();
219: } else {
220: searchStack = null;
221: path = null;
222: }
223:
224: // Iterate over all registered string resource loaders until the
225: // property has been found
226: String string = visitResourceLoaders(key, path, searchStack,
227: locale, style);
228:
229: // If a property value has been found, than replace the placeholder
230: // and we are done
231: if (string != null) {
232: return substitutePropertyExpressions(component, string,
233: model);
234: }
235:
236: // Resource not found, so handle missing resources based on application
237: // configuration
238: final IResourceSettings resourceSettings = application
239: .getResourceSettings();
240: if (resourceSettings.getUseDefaultOnMissingResource()
241: && (defaultValue != null)) {
242: return defaultValue;
243: }
244:
245: if (resourceSettings.getThrowExceptionOnMissingResource()) {
246: AppendingStringBuffer message = new AppendingStringBuffer(
247: "Unable to find resource: " + key);
248: if (component != null) {
249: message.append(" for component: ");
250: message.append(component.getPageRelativePath());
251: }
252: throw new MissingResourceException(message.toString(),
253: (component != null ? component.getClass().getName()
254: : ""), key);
255: } else {
256: return "[Warning: String resource for '" + key
257: + "' not found]";
258: }
259: }
260:
261: /**
262: * Note: This implementation does NOT perform variable substitution
263: *
264: * @param key
265: * The key to obtain the resource for
266: * @param searchStack
267: * A stack of classes to get the resource for
268: * @param path
269: * The component id path relative to the page
270: * @param locale
271: * The locale to get the resource for (optional)
272: * @param style
273: * The style to get the resource for (optional) (see
274: * {@link wicket.Session})
275: * @return The string resource
276: * @throws MissingResourceException
277: * If resource not found and configuration dictates that
278: * exception should be thrown
279: */
280: public String getString(final String key, final String path,
281: final List searchStack, final Locale locale,
282: final String style) throws MissingResourceException {
283: if (searchStack == null) {
284: throw new IllegalArgumentException(
285: "Parameter 'searchStack' must have at least one entry");
286: }
287:
288: // The top element
289: Class componentClass = null;
290: if (searchStack.size() > 0) {
291: componentClass = (Class) searchStack.get(0);
292: }
293:
294: // Get the property by iterating over the register string resouce loader
295: // until the property has been found. Null, if not found.
296: String string = visitResourceLoaders(key, path, searchStack,
297: locale, style);
298: return string;
299: }
300:
301: /**
302: * Traverse the component hierachy up to the Page and add each component
303: * class to the list (stack) returned
304: *
305: * @param component
306: * The component to evaluate
307: * @return The stack of classes
308: */
309: private List getComponentStack(final Component component) {
310: // No component, no stack
311: if (component == null) {
312: return null;
313: }
314:
315: // Build the search stack
316: final List searchStack = new ArrayList();
317: searchStack.add(component.getClass());
318:
319: if (!(component instanceof Page)) {
320: // Add all the component on the way to the Page
321: MarkupContainer container = component.getParent();
322: while (container != null) {
323: searchStack.add(container.getClass());
324: if (container instanceof Page) {
325: break;
326: }
327:
328: container = container.getParent();
329: }
330: }
331: return searchStack;
332: }
333:
334: /**
335: * Helper method to handle preoprty variable substituion in strings.
336: *
337: * @param component
338: * The component requesting a model value
339: * @param string
340: * The string to substitute into
341: * @param model
342: * The model
343: * @return The resulting string
344: */
345: private String substitutePropertyExpressions(
346: final Component component, final String string,
347: final IModel model) {
348: if ((string != null) && (model != null)) {
349: return PropertyVariableInterpolator.interpolate(string,
350: model.getObject(component));
351: }
352: return string;
353: }
354:
355: /**
356: * For each StringResourceLoader registered with the application, load the
357: * properties file associated with the classes in the searchStack, the
358: * locale and the style. The searchStack is traversed in reverse order.
359: * <p>
360: * The property is identified by the 'key' or 'path'+'key'. 'path' is
361: * shortened (last element removed) to always represent the page relative
362: * path of the original component associate with it.
363: *
364: * @param key
365: * The key to obtain the resource for
366: * @param path
367: * The component id path relative to the page
368: * @param searchStack
369: * A stack of classes to get the resource for
370: * @param locale
371: * The locale to get the resource for (optional)
372: * @param style
373: * The style to get the resource for (optional) (see
374: * {@link wicket.Session})
375: * @return The string resource
376: */
377: private String visitResourceLoaders(final String key,
378: final String path, final List searchStack,
379: final Locale locale, final String style) {
380: // Search each loader in turn and return the string if it is found
381: final Iterator iterator = application.getResourceSettings()
382: .getStringResourceLoaders().iterator();
383:
384: // The return value
385: String string = null;
386:
387: // Iterate until a property has been found
388: while (iterator.hasNext() && (string == null)) {
389: IStringResourceLoader loader = (IStringResourceLoader) iterator
390: .next();
391:
392: // The key prefix is equal to the component path relativ to the
393: // current component on the top of the stack.
394: String prefix = path;
395: if ((searchStack != null) && (searchStack.size() > 0)) {
396: // Walk the component hierarchy down from page to the component
397: for (int i = searchStack.size() - 1; (i >= 0)
398: && (string == null); i--) {
399: Class clazz = (Class) searchStack.get(i);
400:
401: // First check if a property with the 'key' provided by the
402: // user
403: // is available.
404: string = loader.loadStringResource(clazz, key,
405: locale, style);
406:
407: // If not, prepend the component relativ path to the key
408: if ((string == null) && (path != null)
409: && (prefix.length() > 0)) {
410: string = loader.loadStringResource(clazz,
411: prefix + '.' + key, locale, style);
412:
413: // If still not found, adjust the component relativ path
414: // for the next component on the path from the page
415: // down.
416: if (string == null) {
417: prefix = Strings.afterFirst(prefix, '.');
418: }
419: }
420: }
421: } else {
422: // A default string resource loader, e.g. the
423: // ApplicationStringResourceLoader,
424: // does not necessarily require the component hierachy
425: string = loader.loadStringResource(null, key, locale,
426: style);
427: if ((string == null) && (path != null)
428: && (prefix.length() > 0)) {
429: string = loader.loadStringResource(null, prefix
430: + '.' + key, locale, style);
431: }
432: }
433: }
434:
435: return string;
436: }
437: }
|