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: * $Header:$
018: */
019: package org.apache.beehive.netui.script.common;
020:
021: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
022: import org.apache.beehive.netui.util.internal.ServletUtils;
023:
024: import java.util.ArrayList;
025: import java.util.Set;
026: import java.util.Iterator;
027: import java.util.Locale;
028: import java.util.Enumeration;
029: import java.util.HashMap;
030: import java.util.ResourceBundle;
031: import javax.servlet.http.HttpServletRequest;
032: import javax.servlet.http.HttpSession;
033: import javax.servlet.ServletContext;
034:
035: import org.apache.struts.Globals;
036: import org.apache.struts.config.MessageResourcesConfig;
037: import org.apache.struts.config.ModuleConfig;
038: import org.apache.struts.util.MessageResources;
039:
040: import org.apache.beehive.netui.script.common.bundle.BundleNode;
041: import org.apache.beehive.netui.script.common.bundle.BundleNodeFactory;
042: import org.apache.beehive.netui.util.logging.Logger;
043: import org.apache.beehive.netui.pageflow.internal.InternalUtils;
044:
045: /**
046: * Provide a {@link java.util.Map} of {@link org.apache.beehive.netui.script.common.BundleMap.BundleNodeMap}
047: * objects that can expose various implementations of {@link BundleNode} to
048: * expression languages. <p/> This {@link java.util.Map} implementation is
049: * optimized for read as the entrySet() is created lazily. In addition, the
050: * entrySet does not contain all possible BundleNodeMap objects as named
051: * "message-resources" bundles are discovered at runtime and requested by name.
052: * <p/>
053: */
054: public class BundleMap extends AbstractScriptableMap {
055:
056: public static final String DEFAULT_STRUTS_BUNDLE_NAME = "default";
057: private static final Logger LOGGER = Logger
058: .getInstance(BundleMap.class);
059:
060: private HashMap _registeredBundles = null;
061:
062: private HttpServletRequest _servletRequest = null;
063: private ServletContext _servletContext = null;
064:
065: /**
066: * Create a BundleMap object that is used for data binding to resource
067: * bundles.
068: *
069: * @param servletRequest the current {@link javax.servlet.http.HttpServletRequest} object
070: * @param servletContext a {@link javax.servlet.ServletContext} object that facilitates binding to resource bundles
071: * declared in Struts modules
072: */
073: public BundleMap(HttpServletRequest servletRequest,
074: ServletContext servletContext) {
075: assert servletRequest != null;
076: assert servletContext != null;
077:
078: _servletRequest = servletRequest;
079: _servletContext = servletContext;
080:
081: _registeredBundles = new HashMap();
082: }
083:
084: public void registerResourceBundle(String name,
085: String resourcePath, Locale forcedLocale) {
086: if (_registeredBundles == null)
087: _registeredBundles = new HashMap();
088:
089: if (LOGGER.isInfoEnabled()
090: && _registeredBundles.containsKey(name))
091: LOGGER.info("The bundle map already contains a key \""
092: + name + "\" overwriting the previous value.");
093:
094: Locale locale = forcedLocale != null ? forcedLocale
095: : InternalUtils.lookupLocale(_servletRequest);
096: ResourceBundle resourceBundle = ResourceBundle.getBundle(
097: resourcePath, locale);
098: BundleNode bundle = BundleNodeFactory.getInstance()
099: .getResourceBundleNode(name, resourceBundle, locale);
100: _registeredBundles.put(name, bundle);
101: }
102:
103: public Object get(Object key) {
104: if (key == null)
105: throw new NullPointerException(
106: "Binding to a resource bundle does not accept a null key");
107:
108: BundleNodeMap map = lookupScriptableBundle(key.toString());
109: if (map == null) {
110: /* handleBundleNotFound will throw an exception when the message key isn't found */
111: handleBundleNotFound(key.toString());
112: return null;
113: } else
114: return map;
115: }
116:
117: /**
118: * Implementation of Map.containsKey for the bundle implicit object.
119: *
120: * This method is required by JSP 2.0 EL and performs the lookups of the
121: * various available bundles which have been registered either explicitly or
122: * implicitly.
123: *
124: * @param key The name of a bundle to lookup
125: * @return <code>true</code> if the bundle is available; <code>false</code> otherwise
126: */
127: public boolean containsKey(Object key) {
128: if (key == null)
129: throw new NullPointerException(
130: "Binding to a resource bundle does not accept a null key");
131:
132: BundleNodeMap map = lookupScriptableBundle(key.toString());
133: return map != null;
134: }
135:
136: public Set entrySet() {
137: ArrayList entries = new ArrayList();
138:
139: /* add BundleNode objects that have been accessed */
140: if (_registeredBundles != null) {
141: Iterator iterator = _registeredBundles.keySet().iterator();
142: while (iterator.hasNext()) {
143: Object key = iterator.next();
144: entries.add(new BundleNodeEntry(key));
145: }
146: }
147:
148: MessageResources resources = null;
149:
150: resources = lookupDefaultStrutsBundle();
151: if (resources != null)
152: entries
153: .add(new BundleNodeEntry(DEFAULT_STRUTS_BUNDLE_NAME));
154:
155: ModuleConfig moduleConfig = lookupCurrentModuleConfig();
156: if (moduleConfig != null) {
157: MessageResourcesConfig[] mrs = moduleConfig
158: .findMessageResourcesConfigs();
159: for (int i = 0; i < mrs.length; i++) {
160: String resourceKey = mrs[i].getKey()
161: + moduleConfig.getPrefix();
162: resources = lookupStrutsBundle(resourceKey);
163: entries.add(new BundleNodeEntry(mrs[i].getKey()));
164: }
165: }
166:
167: return new EntrySet((Entry[]) entries.toArray(new Entry[] {}));
168: }
169:
170: /*
171: */
172: private BundleNodeMap lookupScriptableBundle(String name) {
173: BundleNodeMap map = null;
174:
175: /* check to see if the bundle was explicitly registered */
176: if (_registeredBundles != null
177: && _registeredBundles.containsKey(name)) {
178: map = new BundleNodeMap(name,
179: (BundleNode) _registeredBundles.get(name));
180: } else if (name.equals(DEFAULT_STRUTS_BUNDLE_NAME)) {
181: MessageResources resources = lookupDefaultStrutsBundle();
182: if (resources != null) {
183: BundleNode bundleNode = BundleNodeFactory.getInstance()
184: .getStrutsBundleNode(name, resources,
185: retrieveUserLocale());
186: map = new BundleNodeMap(name, bundleNode);
187: }
188: } else if (_servletContext.getAttribute(name) != null) {
189: MessageResources resources = lookupStrutsBundle(name);
190: if (resources != null) {
191: BundleNode bundleNode = BundleNodeFactory.getInstance()
192: .getStrutsBundleNode(name, resources,
193: retrieveUserLocale());
194: map = new BundleNodeMap(name, bundleNode);
195: }
196: } else {
197: ModuleConfig moduleConfig = lookupCurrentModuleConfig();
198: if (moduleConfig != null) {
199: MessageResourcesConfig[] mrs = moduleConfig
200: .findMessageResourcesConfigs();
201: if (mrs != null) {
202: for (int i = 0; i < mrs.length; i++) {
203: /* skip the default bundle */
204: if (mrs[i].getKey()
205: .equals(Globals.MESSAGES_KEY))
206: continue;
207: else if (mrs[i].getKey().equals(name)) {
208: String resourceKey = mrs[i].getKey()
209: + moduleConfig.getPrefix();
210: MessageResources resources = lookupStrutsBundle(resourceKey);
211: BundleNode bundleNode = BundleNodeFactory
212: .getInstance().getStrutsBundleNode(
213: name, resources,
214: retrieveUserLocale());
215: map = new BundleNodeMap(name, bundleNode);
216: break;
217: }
218: }
219: }
220: }
221: }
222:
223: return map;
224: }
225:
226: /**
227: * Lookup the "default" resource bundle for the current Struts module.
228: *
229: * @return a MessageResources object if a "default" bundle exists.
230: * <code>null</code> otherwise
231: */
232: private MessageResources lookupDefaultStrutsBundle() {
233: Object value = _servletRequest
234: .getAttribute(Globals.MESSAGES_KEY);
235: if (value instanceof MessageResources)
236: return (MessageResources) value;
237: else {
238: if (value != null)
239: LOGGER
240: .warn("Can not resolve the default module bundle."
241: + " The object resolved from the request is of type "
242: + value.getClass().toString());
243: return null;
244: }
245: }
246:
247: /**
248: * Lookup a specific resource bundle for the current Struts module.
249: *
250: * @param name
251: * the name of the resource bundle to lookup
252: * @return a MessageResources object if a bundle matching the given name
253: * exists. <code>null</code> otherwise.
254: */
255: private MessageResources lookupStrutsBundle(String name) {
256: Object value = _servletContext.getAttribute(name);
257: if (value instanceof MessageResources)
258: return (MessageResources) value;
259: else {
260: if (value != null)
261: LOGGER
262: .warn("Can not resolve module bundle with name \""
263: + name
264: + "\". The object resolved from ServletContext is of type "
265: + value.getClass().toString());
266: return null;
267: }
268: }
269:
270: private ModuleConfig lookupCurrentModuleConfig() {
271: return (ModuleConfig) _servletRequest
272: .getAttribute(Globals.MODULE_KEY);
273: }
274:
275: private void handleBundleNotFound(String name) {
276:
277: /* At this point, no message bundle could be found. Throw an error that contains a
278: descriptive message about the bundles that are available
279: */
280: String registeredBundles = formatBundleNames(createBundleList());
281: String strutsBundles = formatBundleNames(createStrutsBundleList());
282:
283: String msg = "The bundle named \""
284: + name
285: + "\" was not found in the list of registered bundles with names "
286: + registeredBundles + " or implicit bundle names "
287: + strutsBundles + ".";
288:
289: LOGGER.error(msg);
290: throw new RuntimeException(msg);
291: }
292:
293: private String formatBundleNames(String[] names) {
294: InternalStringBuilder sb = new InternalStringBuilder(128);
295: sb.append("[");
296: for (int i = 0; i < names.length; i++) {
297: if (i > 0)
298: sb.append(", ");
299: sb.append(names[i]);
300: }
301: sb.append("]");
302:
303: return sb.toString();
304: }
305:
306: private String[] createBundleList() {
307: String[] names = null;
308: if (_registeredBundles != null) {
309: names = new String[_registeredBundles.size()];
310: Iterator iterator = _registeredBundles.keySet().iterator();
311: for (int i = 0; iterator.hasNext(); i++) {
312: names[i] = iterator.next().toString();
313: }
314: }
315:
316: return names;
317: }
318:
319: private String[] createStrutsBundleList() {
320: String[] names = null;
321: ModuleConfig config = lookupCurrentModuleConfig();
322: if (config != null) {
323: MessageResourcesConfig[] mrs = config
324: .findMessageResourcesConfigs();
325: names = new String[mrs.length];
326: if (mrs != null) {
327: for (int i = 0; i < mrs.length; i++) {
328: if (mrs[i].getKey().equals(Globals.MESSAGES_KEY))
329: names[i] = DEFAULT_STRUTS_BUNDLE_NAME;
330: else
331: names[i] = mrs[i].getKey() + config.getPrefix();
332: }
333: }
334: }
335: return names;
336: }
337:
338: /**
339: * Utility method that discovers the {@link java.util.Locale} for the
340: * current request.
341: *
342: * @return the {@link java.util.Locale} to use when looking-up strings while data binding to resource bundles
343: */
344: private Locale retrieveUserLocale() {
345: return InternalUtils.lookupLocale(_servletRequest);
346: }
347:
348: final class BundleNodeEntry extends Entry {
349: BundleNodeEntry(Object key) {
350: super (key, null);
351: }
352:
353: public Object getValue() {
354: assert getKey() instanceof String;
355:
356: String key = (String) getKey();
357: return lookupScriptableBundle(key);
358: }
359: }
360:
361: /**
362: * Provide a {@link java.util.Map} implementation that exposes a
363: * {@link org.apache.beehive.netui.script.common.bundle.BundleNode}
364: * object to an expression language as a Map. Access to the values in the
365: * map is by key and depends on the implementation of the BundleNode. <p/>
366: * Access is read optimized and the complete entrySet() is only constructed
367: * when needed.
368: */
369: final class BundleNodeMap extends AbstractScriptableMap {
370:
371: private String _propertiesName = null;
372: private BundleNode _bundle = null;
373: private Set _entrySet = null;
374:
375: BundleNodeMap(String propertiesName, BundleNode bundle) {
376: assert bundle != null;
377: assert propertiesName != null;
378:
379: _bundle = bundle;
380: _propertiesName = propertiesName;
381: }
382:
383: public Set entrySet() {
384: if (_entrySet == null) {
385: ArrayList list = new ArrayList();
386: Enumeration enumeration = _bundle.getKeys();
387: while (enumeration.hasMoreElements()) {
388: String key = (String) enumeration.nextElement();
389: String msg = _bundle.getString(key);
390: list.add(new Entry(key, msg));
391: }
392: _entrySet = new EntrySet((Entry[]) list
393: .toArray(new Entry[] {}));
394: }
395:
396: return _entrySet;
397: }
398:
399: public Object get(Object key) {
400: if (key == null)
401: throw new NullPointerException(
402: "Bundle data binding does not accept a null key");
403:
404: String result = _bundle.getString(key.toString());
405: if (result == null) {
406: String msg = "The bundle property name \""
407: + key
408: + "\" could not be found in the properties bundle \""
409: + _propertiesName + "\".";
410: LOGGER.error(msg);
411: throw new IllegalArgumentException(msg);
412: } else
413: return result;
414: }
415:
416: public boolean containsKey(Object key) {
417: if (key == null)
418: return false;
419: else
420: return _bundle.getString(key.toString()) != null;
421: }
422:
423: public String toString() {
424: return _bundle != null ? _bundle.toString()
425: : "BundleMap contains an empty BundleNode";
426: }
427: }
428: }
|