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.cocoon.i18n;
018:
019: import org.apache.avalon.framework.activity.Disposable;
020: import org.apache.avalon.framework.component.ComponentException;
021: import org.apache.avalon.framework.configuration.Configurable;
022: import org.apache.avalon.framework.configuration.Configuration;
023: import org.apache.avalon.framework.configuration.ConfigurationException;
024: import org.apache.avalon.framework.logger.AbstractLogEnabled;
025: import org.apache.avalon.framework.service.ServiceException;
026: import org.apache.avalon.framework.service.ServiceManager;
027: import org.apache.avalon.framework.service.Serviceable;
028: import org.apache.avalon.framework.thread.ThreadSafe;
029: import org.apache.excalibur.source.Source;
030: import org.apache.excalibur.source.SourceResolver;
031: import org.apache.excalibur.store.Store;
032:
033: import org.apache.cocoon.util.NetUtils;
034:
035: import java.io.IOException;
036: import java.util.Collections;
037: import java.util.HashMap;
038: import java.util.Locale;
039: import java.util.Map;
040:
041: /**
042: * This is the XMLResourceBundleFactory, the method for getting and creating
043: * XMLResourceBundles.
044: *
045: * @author <a href="mailto:mengelhart@earthtrip.com">Mike Engelhart</a>
046: * @author <a href="mailto:neeme@one.lv">Neeme Praks</a>
047: * @author <a href="mailto:oleg@one.lv">Oleg Podolsky</a>
048: * @author <a href="mailto:kpiroumian@apache.org">Konstantin Piroumian</a>
049: * @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
050: * @version $Id: XMLResourceBundleFactory.java 433543 2006-08-22 06:22:54Z crossley $
051: */
052: public class XMLResourceBundleFactory extends AbstractLogEnabled
053: implements BundleFactory, Serviceable, Configurable,
054: Disposable, ThreadSafe {
055:
056: /**
057: * Root directory to all bundle names
058: */
059: private String directory;
060:
061: /**
062: * Reload check interval in milliseconds.
063: * Defaults to 60000 (1 minute), use <code>-1</code> to
064: * disable reloads and <code>0</code> to check for modifications
065: * on each catalogue request.
066: */
067: private long interval;
068:
069: /**
070: * Service Manager
071: */
072: protected ServiceManager manager;
073:
074: /**
075: * Source resolver
076: */
077: protected SourceResolver resolver;
078:
079: /**
080: * Store of the loaded bundles
081: */
082: protected Store cache;
083:
084: //
085: // Lifecycle
086: //
087:
088: public void service(ServiceManager manager) throws ServiceException {
089: this .manager = manager;
090: this .resolver = (SourceResolver) this .manager
091: .lookup(SourceResolver.ROLE);
092: }
093:
094: /**
095: * Configure the component.
096: *
097: * @param configuration the configuration
098: */
099: public void configure(Configuration configuration)
100: throws ConfigurationException {
101: this .directory = configuration.getChild(
102: ConfigurationKeys.ROOT_DIRECTORY).getValue("");
103:
104: String cacheRole = configuration.getChild(
105: ConfigurationKeys.STORE_ROLE).getValue(
106: Store.TRANSIENT_STORE);
107: try {
108: this .cache = (Store) this .manager.lookup(cacheRole);
109: } catch (ServiceException e) {
110: throw new ConfigurationException("Unable to lookup store '"
111: + cacheRole + "'");
112: }
113:
114: this .interval = configuration.getChild(
115: ConfigurationKeys.RELOAD_INTERVAL).getValueAsLong(
116: 60000L);
117:
118: if (getLogger().isDebugEnabled()) {
119: getLogger().debug(
120: "Bundle directory '" + this .directory + "'");
121: getLogger().debug("Store role '" + cacheRole + "'");
122: }
123: }
124:
125: /**
126: * Disposes this component.
127: */
128: public void dispose() {
129: this .manager.release(this .resolver);
130: this .manager.release(this .cache);
131: this .resolver = null;
132: this .cache = null;
133: this .manager = null;
134: }
135:
136: //
137: // BundleFactory Interface
138: //
139:
140: /**
141: * Returns the root directory to all bundles.
142: *
143: * @return the directory path
144: */
145: protected String getDirectory() {
146: return this .directory;
147: }
148:
149: /**
150: * Select a bundle based on the bundle name and the locale name.
151: *
152: * @param name bundle name
153: * @param locale locale name
154: * @return the bundle
155: * @exception ComponentException if a bundle is not found
156: */
157: public Bundle select(String name, String locale)
158: throws ComponentException {
159: return select(getDirectory(), name, locale);
160: }
161:
162: /**
163: * Select a bundle based on the bundle name and the locale.
164: *
165: * @param name bundle name
166: * @param locale locale
167: * @return the bundle
168: * @exception ComponentException if a bundle is not found
169: */
170: public Bundle select(String name, Locale locale)
171: throws ComponentException {
172: return select(getDirectory(), name, locale);
173: }
174:
175: /**
176: * Select a bundle based on the catalogue base location, bundle name,
177: * and the locale name.
178: *
179: * @param directory catalogue base location (URI)
180: * @param name bundle name
181: * @param localeName locale name
182: * @return the bundle
183: * @exception ComponentException if a bundle is not found
184: */
185: public Bundle select(String directory, String name,
186: String localeName) throws ComponentException {
187: return select(directory, name, new Locale(localeName,
188: localeName));
189: }
190:
191: /**
192: * Select a bundle based on the catalogue base location, bundle name,
193: * and the locale.
194: *
195: * @param directory catalogue base location (URI)
196: * @param name bundle name
197: * @param locale locale
198: * @return the bundle
199: * @exception ComponentException if a bundle is not found
200: */
201: public Bundle select(String directory, String name, Locale locale)
202: throws ComponentException {
203: return select(new String[] { directory }, name, locale);
204: }
205:
206: /**
207: * Select a bundle based on the catalogue base location, bundle name,
208: * and the locale.
209: *
210: * @param directories catalogue base location (URI)
211: * @param name bundle name
212: * @param locale locale
213: * @return the bundle
214: * @exception ComponentException if a bundle is not found
215: */
216: public Bundle select(String[] directories, String name,
217: Locale locale) throws ComponentException {
218: Bundle bundle = _select(directories, 0, name, locale);
219: if (bundle == null) {
220: throw new ComponentException(name,
221: "Unable to locate resource: " + name);
222: }
223: return bundle;
224: }
225:
226: public void release(Bundle bundle) {
227: // Do nothing
228: }
229:
230: //
231: // Implementation
232: //
233:
234: /**
235: * Select a bundle based on bundle name and locale.
236: *
237: * @param directories catalogue location(s)
238: * @param name bundle name
239: * @param locale locale
240: * @return the bundle
241: */
242: private XMLResourceBundle _select(String[] directories, int index,
243: String name, Locale locale) throws ComponentException {
244: if (getLogger().isDebugEnabled()) {
245: getLogger().debug(
246: "Selecting from: " + name + ", locale: " + locale
247: + ", directory: " + directories[index]);
248: }
249:
250: final String cacheKey = "XRB"
251: + getCacheKey(directories, index, name, locale);
252:
253: XMLResourceBundle bundle = selectCached(cacheKey);
254: if (bundle == null) {
255: synchronized (this ) {
256: bundle = selectCached(cacheKey);
257: if (bundle == null) {
258: boolean localeAvailable = (locale != null && !locale
259: .getLanguage().equals(""));
260: index++;
261:
262: // Find parent bundle first
263: XMLResourceBundle parent = null;
264: if (localeAvailable && index == directories.length) {
265: // all directories have been searched with this locale,
266: // now start again with the first directory and the parent locale
267: parent = _select(directories, 0, name,
268: getParentLocale(locale));
269: } else if (index < directories.length) {
270: // there are directories left to search for with this locale
271: parent = _select(directories, index, name,
272: locale);
273: }
274:
275: // Create this bundle (if source exists) and pass parent to it.
276: final String sourceURI = getSourceURI(
277: directories[index - 1], name, locale);
278: bundle = _create(sourceURI, locale, parent);
279: updateCache(cacheKey, bundle);
280: }
281: }
282: }
283: return bundle;
284: }
285:
286: /**
287: * Constructs new bundle.
288: *
289: * <p>
290: * If there is a problem loading the bundle, created bundle will be empty.
291: *
292: * @param sourceURI source URI of the XML resource bundle
293: * @param locale locale of the bundle
294: * @param parent parent bundle, if any
295: * @return the bundle
296: */
297: private XMLResourceBundle _create(String sourceURI, Locale locale,
298: XMLResourceBundle parent) {
299: if (getLogger().isDebugEnabled()) {
300: getLogger().debug("Creating bundle <" + sourceURI + ">");
301: }
302:
303: XMLResourceBundle bundle = new XMLResourceBundle(sourceURI,
304: locale, parent);
305: bundle.enableLogging(getLogger());
306: bundle.reload(this .resolver, this .interval);
307: return bundle;
308: }
309:
310: /**
311: * Returns the next locale up the parent hierarchy.
312: * E.g. the parent of new Locale("en","us","mac") would be
313: * new Locale("en", "us", "").
314: *
315: * @param locale the locale
316: * @return the parent locale
317: */
318: protected Locale getParentLocale(Locale locale) {
319: Locale newloc;
320: if (locale.getVariant().length() == 0) {
321: if (locale.getCountry().length() == 0) {
322: newloc = new Locale("", "", "");
323: } else {
324: newloc = new Locale(locale.getLanguage(), "", "");
325: }
326: } else {
327: newloc = new Locale(locale.getLanguage(), locale
328: .getCountry(), "");
329: }
330: return newloc;
331: }
332:
333: /**
334: * Creates a cache key for the bundle.
335: * @return the cache key
336: */
337: protected String getCacheKey(String[] directories, int index,
338: String name, Locale locale) throws ComponentException {
339: StringBuffer cacheKey = new StringBuffer();
340: if (index < directories.length) {
341: cacheKey.append(":");
342: cacheKey.append(getSourceURI(directories[index], name,
343: locale));
344: index++;
345: cacheKey.append(getCacheKey(directories, index, name,
346: locale));
347: } else if ((locale != null && !locale.getLanguage().equals(""))) {
348: cacheKey.append(getCacheKey(directories, 0, name,
349: getParentLocale(locale)));
350: }
351: return cacheKey.toString();
352: }
353:
354: /**
355: * Maps a bundle name and locale to a bundle source URI.
356: * If you need a different mapping, then just override this method.
357: *
358: * @param base the base URI for the catalogues
359: * @param name the name of the catalogue
360: * @param locale the locale of the bundle
361: * @return the source URI for the bundle
362: */
363: protected String getSourceURI(String base, String name,
364: Locale locale) throws ComponentException {
365: // If base is null default to the current location
366: if (base == null) {
367: base = "";
368: }
369:
370: // Resolve base URI
371: Source src = null;
372: Map parameters = Collections.EMPTY_MAP;
373: StringBuffer sb = new StringBuffer();
374: try {
375: src = this .resolver.resolveURI(base);
376:
377: // Deparameterize base URL before adding catalogue name
378: String uri = NetUtils.deparameterize(src.getURI(),
379: parameters = new HashMap(7));
380:
381: // Append trailing slash
382: sb.append(uri);
383: if (!uri.endsWith("/")) {
384: sb.append('/');
385: }
386:
387: } catch (IOException e) {
388: throw new ComponentException(
389: "Cannot resolve catalogue base URI <" + base + ">",
390: name, e);
391: } finally {
392: this .resolver.release(src);
393: }
394:
395: // Append catalogue name
396: sb.append(name);
397:
398: // Append catalogue locale
399: if (locale != null) {
400: if (!locale.getLanguage().equals("")) {
401: sb.append("_");
402: sb.append(locale.getLanguage());
403: }
404: if (!locale.getCountry().equals("")) {
405: sb.append("_");
406: sb.append(locale.getCountry());
407: }
408: if (!locale.getVariant().equals("")) {
409: sb.append("_");
410: sb.append(locale.getVariant());
411: }
412: }
413: sb.append(".xml");
414:
415: // Reconstruct complete bundle URI with parameters
416: String uri = NetUtils.parameterize(sb.toString(), parameters);
417:
418: if (getLogger().isDebugEnabled()) {
419: getLogger().debug(
420: "Resolved name: " + name + ", locale: " + locale
421: + " --> " + uri);
422: }
423: return uri;
424: }
425:
426: /**
427: * Selects a bundle from the cache, and reloads it if needed.
428: *
429: * @param cacheKey caching key of the bundle
430: * @return the cached bundle; null, if not found
431: */
432: protected XMLResourceBundle selectCached(String cacheKey) {
433: XMLResourceBundle bundle = (XMLResourceBundle) this .cache
434: .get(cacheKey);
435:
436: if (bundle != null && this .interval != -1) {
437: // Reload this bundle and all parent bundles, as necessary
438: for (XMLResourceBundle b = bundle; b != null; b = (XMLResourceBundle) b.parent) {
439: b.reload(this .resolver, this .interval);
440: }
441: }
442:
443: return bundle;
444: }
445:
446: /**
447: * Stores bundle in the cache.
448: *
449: * @param cacheKey caching key of the bundle
450: * @param bundle bundle to be placed in the cache
451: */
452: protected void updateCache(String cacheKey, XMLResourceBundle bundle) {
453: try {
454: this .cache.store(cacheKey, bundle);
455: } catch (IOException e) {
456: getLogger().error(
457: "Bundle <" + bundle.getSourceURI()
458: + ">: unable to store.", e);
459: }
460: }
461: }
|