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.resource.loader;
018:
019: import java.util.ArrayList;
020: import java.util.List;
021: import java.util.Locale;
022:
023: import org.apache.wicket.Application;
024: import org.apache.wicket.Component;
025: import org.apache.wicket.MarkupContainer;
026: import org.apache.wicket.Page;
027: import org.apache.wicket.markup.html.WebComponent;
028: import org.apache.wicket.markup.html.WebMarkupContainer;
029: import org.apache.wicket.markup.html.WebPage;
030: import org.apache.wicket.resource.IPropertiesFactory;
031: import org.apache.wicket.resource.Properties;
032: import org.apache.wicket.util.resource.locator.ResourceNameIterator;
033: import org.apache.wicket.util.string.Strings;
034: import org.slf4j.Logger;
035: import org.slf4j.LoggerFactory;
036:
037: /**
038: * This is Wicket's default string resource loader.
039: * <p>
040: * The component based string resource loader attempts to find the resource from
041: * a bundle that corresponds to the supplied component object or one of its
042: * parent containers.
043: * <p>
044: * The search order for resources is built around the containers that hold the
045: * component (if it is not a page). Consider a Page that contains a Panel that
046: * contains a Label. If we pass the Label as the component then resource loading
047: * will first look for the resource against the page, then against the panel and
048: * finally against the label.
049: * <p>
050: * The above search order may seem slightly odd at first, but can be explained
051: * thus: Team A writes a new component X and packages it as a reusable Wicket
052: * component along with all required resources. Team B then creates a new
053: * container component Y that holds a instance of an X. However, Team B wishes
054: * the text to be different to that which was provided with X so rather than
055: * needing to change X, they include override values in the resources for Y.
056: * Finally, Team C makes use of component Y in a page they are writing.
057: * Initially they are happy with the text for Y so they do not include any
058: * override values in the resources for the page. However, after demonstrating
059: * to the customer, the customer requests the text for Y to be different. Team C
060: * need only provide override values against their page and thus do not need to
061: * change Y.
062: * <p>
063: * This implementation is fully aware of both locale and style values when
064: * trying to obtain the appropriate resources.
065: * <p>
066: * In addition to the above search order, each key will be pre-pended with the
067: * relative path of the current component related to the component that is being
068: * searched. E.g. assume a component hierarchy like page1.form1.input1 and your
069: * are requesting a key named 'Required'. Wicket will search the property in the
070: * following order:
071: *
072: * <pre>
073: * page1.properties => form1.input1.Required
074: * page1.properties => Required
075: * form1.properties => input1.Required
076: * form1.properties => Required
077: * input1.properties => Required
078: * myApplication.properties => page1.form1.input1.Required
079: * myApplication.properties => Required
080: * </pre>
081: *
082: * Note that the latter two property files are only checked if the
083: * ClassStringResourceLoader has been registered with Application as well, which
084: * is the default.
085: * <p>
086: * In addition to the above search order, each component that is being searched
087: * for a resource also includes the resources from any parent classes that it
088: * inherits from. For example, PageA extends CommonBasePage which in turn
089: * extends WebPage. When a resource lookup is requested on PageA, the resource
090: * bundle for PageA is first checked. If the resource is not found in this
091: * bundle then the resource bundle for CommonBasePage is checked. This allows
092: * designers of base pages and components to define default sets of string
093: * resources and then developers implementing subclasses to either override or
094: * extend these in their own resource bundle.
095: * <p>
096: * This implementation can be subclassed to implement modified behavior. The new
097: * implementation must be registered with the Application (ResourceSettings)
098: * though.
099: * <p>
100: * You may enable log debug messages for this class to fully understand the
101: * search order.
102: *
103: * @author Chris Turner
104: * @author Juergen Donnerstag
105: */
106: public class ComponentStringResourceLoader implements
107: IStringResourceLoader {
108: /** Log. */
109: private static final Logger log = LoggerFactory
110: .getLogger(ComponentStringResourceLoader.class);
111:
112: /**
113: * Create and initialise the resource loader.
114: */
115: public ComponentStringResourceLoader() {
116: }
117:
118: /**
119: * Get the string resource for the given combination of class, key, locale
120: * and style. The information is obtained from a resource bundle associated
121: * with the provided Class (or one of its super classes).
122: *
123: * @param clazz
124: * The Class to find resources to be loaded
125: * @param key
126: * The key to obtain the string for
127: * @param locale
128: * The locale identifying the resource set to select the strings
129: * from
130: * @param style
131: * The (optional) style identifying the resource set to select
132: * the strings from (see {@link org.apache.wicket.Session})
133: * @return The string resource value or null if resource not found
134: */
135: public String loadStringResource(Class clazz, final String key,
136: final Locale locale, final String style) {
137: if (clazz == null) {
138: return null;
139: }
140:
141: // Load the properties associated with the path
142: IPropertiesFactory propertiesFactory = Application.get()
143: .getResourceSettings().getPropertiesFactory();
144:
145: while (true) {
146: // Create the base path
147: String path = clazz.getName().replace('.', '/');
148:
149: // Iterator over all the combinations
150: ResourceNameIterator iter = new ResourceNameIterator(path,
151: style, locale, "properties,xml");
152: while (iter.hasNext()) {
153: String newPath = (String) iter.next();
154:
155: final Properties props = propertiesFactory.load(clazz,
156: newPath);
157: if (props != null) {
158: // Lookup the value
159: String value = props.getString(key);
160: if (value != null) {
161: if (log.isDebugEnabled()) {
162: log.debug("Found resource from: " + props
163: + "; key: " + key);
164: }
165:
166: return value;
167: }
168: }
169: }
170:
171: // Didn't find the key yet, continue searching if possible
172: if (isStopResourceSearch(clazz)) {
173: break;
174: }
175:
176: // Move to the next superclass
177: clazz = clazz.getSuperclass();
178: }
179:
180: // not found
181: return null;
182: }
183:
184: /**
185: *
186: * @see org.apache.wicket.resource.loader.IStringResourceLoader#loadStringResource(org.apache.wicket.Component,
187: * java.lang.String)
188: */
189: public String loadStringResource(final Component component,
190: final String key) {
191: if (component == null) {
192: return null;
193: }
194:
195: // The return value
196: String string = null;
197: Locale locale = component.getLocale();
198: String style = component.getStyle();
199:
200: // The key prefix is equal to the component path relativ to the
201: // current component on the top of the stack.
202: String prefix = Strings.replaceAll(
203: component.getPageRelativePath(), ":", ".").toString();
204:
205: // The reason why we need to create that stack is because we need to
206: // walk it downwards starting with Page down to the Component
207: List searchStack = getComponentStack(component);
208:
209: // Walk the component hierarchy down from page to the component
210: for (int i = searchStack.size() - 1; (i >= 0)
211: && (string == null); i--) {
212: Class clazz = (Class) searchStack.get(i);
213:
214: // First, try the fully qualified resource name relative to the
215: // component on the path from page down.
216: if ((prefix != null) && (prefix.length() > 0)) {
217: string = loadStringResource(clazz, prefix + '.' + key,
218: locale, style);
219:
220: if (string == null) {
221: prefix = Strings.afterFirst(prefix, '.');
222: }
223: }
224:
225: // If not found, than check if a property with the 'key' provided by
226: // the user can be found.
227: if (string == null) {
228: string = loadStringResource(clazz, key, locale, style);
229: }
230: }
231:
232: return string;
233: }
234:
235: /**
236: * Traverse the component hierachy up to the Page and add each component
237: * class to the list (stack) returned
238: *
239: * @param component
240: * The component to evaluate
241: * @return The stack of classes
242: */
243: private List getComponentStack(final Component component) {
244: // Build the search stack
245: final List searchStack = new ArrayList();
246: searchStack.add(component.getClass());
247:
248: if (!(component instanceof Page)) {
249: // Add all the component on the way to the Page
250: MarkupContainer container = component.getParent();
251: while (container != null) {
252: searchStack.add(container.getClass());
253: if (container instanceof Page) {
254: break;
255: }
256:
257: container = container.getParent();
258: }
259: }
260: return searchStack;
261: }
262:
263: /**
264: * Check the supplied class to see if it is one that we shouldn't bother
265: * further searches up the class hierarchy for properties.
266: *
267: * @param clazz
268: * The class to check
269: * @return Whether to stop the search
270: */
271: protected boolean isStopResourceSearch(final Class clazz) {
272: if (clazz == null || clazz.equals(Object.class)
273: || clazz.equals(Application.class)) {
274: return true;
275: }
276:
277: // Stop at all html markup base classes
278: if (clazz.equals(WebPage.class)
279: || clazz.equals(WebMarkupContainer.class)
280: || clazz.equals(WebComponent.class)) {
281: return true;
282: }
283:
284: // Stop at all wicket base classes
285: return clazz.equals(Page.class)
286: || clazz.equals(MarkupContainer.class)
287: || clazz.equals(Component.class);
288: }
289: }
|