%line | %branch | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
org.apache.turbine.services.localization.TurbineLocalizationService |
|
|
1 | package org.apache.turbine.services.localization; |
|
2 | ||
3 | /* |
|
4 | * Copyright 2001-2005 The Apache Software Foundation. |
|
5 | * |
|
6 | * Licensed under the Apache License, Version 2.0 (the "License") |
|
7 | * you may not use this file except in compliance with the License. |
|
8 | * You may obtain a copy of the License at |
|
9 | * |
|
10 | * http://www.apache.org/licenses/LICENSE-2.0 |
|
11 | * |
|
12 | * Unless required by applicable law or agreed to in writing, software |
|
13 | * distributed under the License is distributed on an "AS IS" BASIS, |
|
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
15 | * See the License for the specific language governing permissions and |
|
16 | * limitations under the License. |
|
17 | */ |
|
18 | ||
19 | import java.util.HashMap; |
|
20 | import java.util.Locale; |
|
21 | import java.util.Map; |
|
22 | import java.util.MissingResourceException; |
|
23 | import java.util.ResourceBundle; |
|
24 | ||
25 | import javax.servlet.http.HttpServletRequest; |
|
26 | ||
27 | import org.apache.commons.configuration.Configuration; |
|
28 | ||
29 | import org.apache.commons.lang.StringUtils; |
|
30 | ||
31 | import org.apache.commons.logging.Log; |
|
32 | import org.apache.commons.logging.LogFactory; |
|
33 | ||
34 | import org.apache.turbine.Turbine; |
|
35 | import org.apache.turbine.services.InitializationException; |
|
36 | import org.apache.turbine.services.TurbineBaseService; |
|
37 | import org.apache.turbine.util.RunData; |
|
38 | ||
39 | /** |
|
40 | * <p>This class is the single point of access to all localization |
|
41 | * resources. It caches different ResourceBundles for different |
|
42 | * Locales.</p> |
|
43 | * |
|
44 | * <p>Usage example:</p> |
|
45 | * |
|
46 | * <blockquote><code><pre> |
|
47 | * LocalizationService ls = (LocalizationService) TurbineServices |
|
48 | * .getInstance().getService(LocalizationService.SERVICE_NAME); |
|
49 | * </pre></code></blockquote> |
|
50 | * |
|
51 | * <p>Then call one of four methods to retrieve a ResourceBundle: |
|
52 | * |
|
53 | * <ul> |
|
54 | * <li>getBundle("MyBundleName")</li> |
|
55 | * <li>getBundle("MyBundleName", httpAcceptLanguageHeader)</li> |
|
56 | * <li>etBundle("MyBundleName", HttpServletRequest)</li> |
|
57 | * <li>getBundle("MyBundleName", Locale)</li> |
|
58 | * <li>etc.</li> |
|
59 | * </ul></p> |
|
60 | * |
|
61 | * @author <a href="mailto:jm@mediaphil.de">Jonas Maurus</a> |
|
62 | * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a> |
|
63 | * @author <a href="mailto:novalidemail@foo.com">Frank Y. Kim</a> |
|
64 | * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a> |
|
65 | * @author <a href="mailto:leonardr@collab.net">Leonard Richardson</a> |
|
66 | * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> |
|
67 | * @version $Id: TurbineLocalizationService.java 264152 2005-08-29 14:50:22Z henning $ |
|
68 | */ |
|
69 | public class TurbineLocalizationService |
|
70 | extends TurbineBaseService |
|
71 | implements LocalizationService |
|
72 | { |
|
73 | /** Logging */ |
|
74 | 0 | private static Log log = LogFactory.getLog(TurbineLocalizationService.class); |
75 | ||
76 | /** |
|
77 | * The value to pass to <code>MessageFormat</code> if a |
|
78 | * <code>null</code> reference is passed to <code>format()</code>. |
|
79 | */ |
|
80 | 0 | private static final Object[] NO_ARGS = new Object[0]; |
81 | ||
82 | /** |
|
83 | * Bundle name keys a Map of the ResourceBundles in this |
|
84 | * service (which is in turn keyed by Locale). |
|
85 | * Key=bundle name |
|
86 | * Value=Hashtable containing ResourceBundles keyed by Locale. |
|
87 | */ |
|
88 | 0 | private Map bundles = null; |
89 | ||
90 | /** |
|
91 | * The list of default bundles to search. |
|
92 | */ |
|
93 | 0 | private String[] bundleNames = null; |
94 | ||
95 | /** |
|
96 | * The name of the default locale to use (includes language and |
|
97 | * country). |
|
98 | */ |
|
99 | 0 | private Locale defaultLocale = null; |
100 | ||
101 | /** The name of the default language to use. */ |
|
102 | 0 | private String defaultLanguage = null; |
103 | ||
104 | /** The name of the default country to use. */ |
|
105 | 0 | private String defaultCountry = null; |
106 | ||
107 | /** |
|
108 | * Constructor. |
|
109 | */ |
|
110 | public TurbineLocalizationService() |
|
111 | 0 | { |
112 | 0 | bundles = new HashMap(); |
113 | 0 | } |
114 | ||
115 | /** |
|
116 | * Called the first time the Service is used. |
|
117 | */ |
|
118 | public void init() |
|
119 | throws InitializationException |
|
120 | { |
|
121 | 0 | Configuration conf = Turbine.getConfiguration(); |
122 | ||
123 | 0 | initBundleNames(null); |
124 | ||
125 | 0 | Locale jvmDefault = Locale.getDefault(); |
126 | ||
127 | 0 | defaultLanguage = conf.getString("locale.default.language", |
128 | jvmDefault.getLanguage()).trim(); |
|
129 | 0 | defaultCountry = conf.getString("locale.default.country", |
130 | jvmDefault.getCountry()).trim(); |
|
131 | ||
132 | 0 | defaultLocale = new Locale(defaultLanguage, defaultCountry); |
133 | 0 | setInit(true); |
134 | 0 | } |
135 | ||
136 | /** |
|
137 | * Initialize list of default bundle names. |
|
138 | * |
|
139 | * @param ignored Ignored. |
|
140 | */ |
|
141 | protected void initBundleNames(String[] ignored) |
|
142 | { |
|
143 | 0 | Configuration conf = Turbine.getConfiguration(); |
144 | 0 | bundleNames = conf.getStringArray("locale.default.bundles"); |
145 | 0 | String name = conf.getString("locale.default.bundle"); |
146 | ||
147 | 0 | if (name != null && name.length() > 0) |
148 | { |
|
149 | // Using old-style single bundle name property. |
|
150 | 0 | if (bundleNames == null || bundleNames.length <= 0) |
151 | { |
|
152 | 0 | bundleNames = new String[] {name}; |
153 | } |
|
154 | else |
|
155 | { |
|
156 | // Prepend "default" bundle name. |
|
157 | 0 | String[] array = new String[bundleNames.length + 1]; |
158 | 0 | array[0] = name; |
159 | 0 | System.arraycopy(bundleNames, 0, array, 1, bundleNames.length); |
160 | 0 | bundleNames = array; |
161 | } |
|
162 | } |
|
163 | 0 | if (bundleNames == null) |
164 | { |
|
165 | 0 | bundleNames = new String[0]; |
166 | } |
|
167 | 0 | } |
168 | ||
169 | /** |
|
170 | * Retrieves the default language (specified in the config file). |
|
171 | */ |
|
172 | public String getDefaultLanguage() |
|
173 | { |
|
174 | 0 | return defaultLanguage; |
175 | } |
|
176 | ||
177 | /** |
|
178 | * Retrieves the default country (specified in the config file). |
|
179 | */ |
|
180 | public String getDefaultCountry() |
|
181 | { |
|
182 | 0 | return defaultCountry; |
183 | } |
|
184 | ||
185 | /** |
|
186 | * Retrieves the name of the default bundle (as specified in the |
|
187 | * config file). |
|
188 | * @see org.apache.turbine.services.localization.LocalizationService#getDefaultBundleName() |
|
189 | */ |
|
190 | public String getDefaultBundleName() |
|
191 | { |
|
192 | 0 | return (bundleNames.length > 0 ? bundleNames[0] : ""); |
193 | } |
|
194 | ||
195 | /** |
|
196 | * @see org.apache.turbine.services.localization.LocalizationService#getBundleNames() |
|
197 | */ |
|
198 | public String[] getBundleNames() |
|
199 | { |
|
200 | 0 | return (String []) bundleNames.clone(); |
201 | } |
|
202 | ||
203 | /** |
|
204 | * This method returns a ResourceBundle given the bundle name |
|
205 | * "DEFAULT" and the default Locale information supplied in |
|
206 | * TurbineProperties. |
|
207 | * |
|
208 | * @return A localized ResourceBundle. |
|
209 | */ |
|
210 | public ResourceBundle getBundle() |
|
211 | { |
|
212 | 0 | return getBundle(getDefaultBundleName(), (Locale) null); |
213 | } |
|
214 | ||
215 | /** |
|
216 | * This method returns a ResourceBundle given the bundle name and |
|
217 | * the default Locale information supplied in TurbineProperties. |
|
218 | * |
|
219 | * @param bundleName Name of bundle. |
|
220 | * @return A localized ResourceBundle. |
|
221 | */ |
|
222 | public ResourceBundle getBundle(String bundleName) |
|
223 | { |
|
224 | 0 | return getBundle(bundleName, (Locale) null); |
225 | } |
|
226 | ||
227 | /** |
|
228 | * This method returns a ResourceBundle given the bundle name and |
|
229 | * the Locale information supplied in the HTTP "Accept-Language" |
|
230 | * header. |
|
231 | * |
|
232 | * @param bundleName Name of bundle. |
|
233 | * @param languageHeader A String with the language header. |
|
234 | * @return A localized ResourceBundle. |
|
235 | */ |
|
236 | public ResourceBundle getBundle(String bundleName, String languageHeader) |
|
237 | { |
|
238 | 0 | return getBundle(bundleName, getLocale(languageHeader)); |
239 | } |
|
240 | ||
241 | /** |
|
242 | * This method returns a ResourceBundle given the Locale |
|
243 | * information supplied in the HTTP "Accept-Language" header which |
|
244 | * is stored in HttpServletRequest. |
|
245 | * |
|
246 | * @param req HttpServletRequest. |
|
247 | * @return A localized ResourceBundle. |
|
248 | */ |
|
249 | public ResourceBundle getBundle(HttpServletRequest req) |
|
250 | { |
|
251 | 0 | return getBundle(getDefaultBundleName(), getLocale(req)); |
252 | } |
|
253 | ||
254 | /** |
|
255 | * This method returns a ResourceBundle given the bundle name and |
|
256 | * the Locale information supplied in the HTTP "Accept-Language" |
|
257 | * header which is stored in HttpServletRequest. |
|
258 | * |
|
259 | * @param bundleName Name of the bundle to use if the request's |
|
260 | * locale cannot be resolved. |
|
261 | * @param req HttpServletRequest. |
|
262 | * @return A localized ResourceBundle. |
|
263 | */ |
|
264 | public ResourceBundle getBundle(String bundleName, HttpServletRequest req) |
|
265 | { |
|
266 | 0 | return getBundle(bundleName, getLocale(req)); |
267 | } |
|
268 | ||
269 | /** |
|
270 | * This method returns a ResourceBundle given the Locale |
|
271 | * information supplied in the HTTP "Accept-Language" header which |
|
272 | * is stored in RunData. |
|
273 | * |
|
274 | * @param data Turbine information. |
|
275 | * @return A localized ResourceBundle. |
|
276 | */ |
|
277 | public ResourceBundle getBundle(RunData data) |
|
278 | { |
|
279 | 0 | return getBundle(getDefaultBundleName(), getLocale(data.getRequest())); |
280 | } |
|
281 | ||
282 | /** |
|
283 | * This method returns a ResourceBundle given the bundle name and |
|
284 | * the Locale information supplied in the HTTP "Accept-Language" |
|
285 | * header which is stored in RunData. |
|
286 | * |
|
287 | * @param bundleName Name of bundle. |
|
288 | * @param data Turbine information. |
|
289 | * @return A localized ResourceBundle. |
|
290 | */ |
|
291 | public ResourceBundle getBundle(String bundleName, RunData data) |
|
292 | { |
|
293 | 0 | return getBundle(bundleName, getLocale(data.getRequest())); |
294 | } |
|
295 | ||
296 | /** |
|
297 | * This method returns a ResourceBundle for the given bundle name |
|
298 | * and the given Locale. |
|
299 | * |
|
300 | * @param bundleName Name of bundle (or <code>null</code> for the |
|
301 | * default bundle). |
|
302 | * @param locale The locale (or <code>null</code> for the locale |
|
303 | * indicated by the default language and country). |
|
304 | * @return A localized ResourceBundle. |
|
305 | */ |
|
306 | public ResourceBundle getBundle(String bundleName, Locale locale) |
|
307 | { |
|
308 | // Assure usable inputs. |
|
309 | 0 | bundleName = (StringUtils.isEmpty(bundleName) ? getDefaultBundleName() : bundleName.trim()); |
310 | 0 | if (locale == null) |
311 | { |
|
312 | 0 | locale = getLocale((String) null); |
313 | } |
|
314 | ||
315 | // Find/retrieve/cache bundle. |
|
316 | 0 | ResourceBundle rb = null; |
317 | 0 | Map bundlesByLocale = (Map) bundles.get(bundleName); |
318 | 0 | if (bundlesByLocale != null) |
319 | { |
|
320 | // Cache of bundles by locale for the named bundle exists. |
|
321 | // Check the cache for a bundle corresponding to locale. |
|
322 | 0 | rb = (ResourceBundle) bundlesByLocale.get(locale); |
323 | ||
324 | 0 | if (rb == null) |
325 | { |
|
326 | // Not yet cached. |
|
327 | 0 | rb = cacheBundle(bundleName, locale); |
328 | } |
|
329 | } |
|
330 | else |
|
331 | { |
|
332 | 0 | rb = cacheBundle(bundleName, locale); |
333 | } |
|
334 | 0 | return rb; |
335 | } |
|
336 | ||
337 | /** |
|
338 | * Caches the named bundle for fast lookups. This operation is |
|
339 | * relatively expesive in terms of memory use, but is optimized |
|
340 | * for run-time speed in the usual case. |
|
341 | * |
|
342 | * @exception MissingResourceException Bundle not found. |
|
343 | */ |
|
344 | private synchronized ResourceBundle cacheBundle(String bundleName, |
|
345 | Locale locale) |
|
346 | throws MissingResourceException |
|
347 | { |
|
348 | 0 | Map bundlesByLocale = (HashMap) bundles.get(bundleName); |
349 | 0 | ResourceBundle rb = (bundlesByLocale == null ? class="keyword">null : |
350 | (ResourceBundle) bundlesByLocale.get(locale)); |
|
351 | ||
352 | 0 | if (rb == null) |
353 | { |
|
354 | 0 | bundlesByLocale = (bundlesByLocale == null ? new HashMap(3) : |
355 | new HashMap(bundlesByLocale)); |
|
356 | try |
|
357 | { |
|
358 | 0 | rb = ResourceBundle.getBundle(bundleName, locale); |
359 | } |
|
360 | 0 | catch (MissingResourceException e) |
361 | { |
|
362 | 0 | rb = findBundleByLocale(bundleName, locale, bundlesByLocale); |
363 | 0 | if (rb == null) |
364 | { |
|
365 | 0 | throw (MissingResourceException) e.fillInStackTrace(); |
366 | } |
|
367 | 0 | } |
368 | ||
369 | 0 | if (rb != null) |
370 | { |
|
371 | // Cache bundle. |
|
372 | 0 | bundlesByLocale.put(rb.getLocale(), rb); |
373 | ||
374 | 0 | Map bundlesByName = new HashMap(bundles); |
375 | 0 | bundlesByName.put(bundleName, bundlesByLocale); |
376 | 0 | this.bundles = bundlesByName; |
377 | } |
|
378 | } |
|
379 | 0 | return rb; |
380 | } |
|
381 | ||
382 | /** |
|
383 | * <p>Retrieves the bundle most closely matching first against the |
|
384 | * supplied inputs, then against the defaults.</p> |
|
385 | * |
|
386 | * <p>Use case: some clients send a HTTP Accept-Language header |
|
387 | * with a value of only the language to use |
|
388 | * (i.e. "Accept-Language: en"), and neglect to include a country. |
|
389 | * When there is no bundle for the requested language, this method |
|
390 | * can be called to try the default country (checking internally |
|
391 | * to assure the requested criteria matches the default to avoid |
|
392 | * disconnects between language and country).</p> |
|
393 | * |
|
394 | * <p>Since we're really just guessing at possible bundles to use, |
|
395 | * we don't ever throw <code>MissingResourceException</code>.</p> |
|
396 | */ |
|
397 | private ResourceBundle findBundleByLocale(String bundleName, Locale locale, |
|
398 | Map bundlesByLocale) |
|
399 | { |
|
400 | 0 | ResourceBundle rb = null; |
401 | 0 | if (!StringUtils.isNotEmpty(locale.getCountry()) && |
402 | defaultLanguage.equals(locale.getLanguage())) |
|
403 | { |
|
404 | /* |
|
405 | * log.debug("Requested language '" + locale.getLanguage() + |
|
406 | * "' matches default: Attempting to guess bundle " + |
|
407 | * "using default country '" + defaultCountry + '\''); |
|
408 | */ |
|
409 | 0 | Locale withDefaultCountry = new Locale(locale.getLanguage(), |
410 | defaultCountry); |
|
411 | 0 | rb = (ResourceBundle) bundlesByLocale.get(withDefaultCountry); |
412 | 0 | if (rb == null) |
413 | { |
|
414 | 0 | rb = getBundleIgnoreException(bundleName, withDefaultCountry); |
415 | } |
|
416 | } |
|
417 | 0 | else if (!StringUtils.isNotEmpty(locale.getLanguage()) && |
418 | defaultCountry.equals(locale.getCountry())) |
|
419 | { |
|
420 | 0 | Locale withDefaultLanguage = new Locale(defaultLanguage, |
421 | locale.getCountry()); |
|
422 | 0 | rb = (ResourceBundle) bundlesByLocale.get(withDefaultLanguage); |
423 | 0 | if (rb == null) |
424 | { |
|
425 | 0 | rb = getBundleIgnoreException(bundleName, withDefaultLanguage); |
426 | } |
|
427 | } |
|
428 | ||
429 | 0 | if (rb == null && !defaultLocale.equals(locale)) |
430 | { |
|
431 | 0 | rb = getBundleIgnoreException(bundleName, defaultLocale); |
432 | } |
|
433 | ||
434 | 0 | return rb; |
435 | } |
|
436 | ||
437 | /** |
|
438 | * Retrieves the bundle using the |
|
439 | * <code>ResourceBundle.getBundle(String, Locale)</code> method, |
|
440 | * returning <code>null</code> instead of throwing |
|
441 | * <code>MissingResourceException</code>. |
|
442 | */ |
|
443 | private ResourceBundle getBundleIgnoreException(String bundleName, |
|
444 | Locale locale) |
|
445 | { |
|
446 | try |
|
447 | { |
|
448 | 0 | return ResourceBundle.getBundle(bundleName, locale); |
449 | } |
|
450 | 0 | catch (MissingResourceException ignored) |
451 | { |
|
452 | 0 | return null; |
453 | } |
|
454 | } |
|
455 | ||
456 | /** |
|
457 | * This method sets the name of the first bundle in the search |
|
458 | * list (the "default" bundle). |
|
459 | * |
|
460 | * @param defaultBundle Name of default bundle. |
|
461 | */ |
|
462 | public void setBundle(String defaultBundle) |
|
463 | { |
|
464 | 0 | if (bundleNames.length > 0) |
465 | { |
|
466 | 0 | bundleNames[0] = defaultBundle; |
467 | } |
|
468 | else |
|
469 | { |
|
470 | 0 | synchronized (this) |
471 | { |
|
472 | 0 | if (bundleNames.length <= 0) |
473 | { |
|
474 | 0 | bundleNames = new String[] {defaultBundle}; |
475 | } |
|
476 | 0 | } |
477 | } |
|
478 | 0 | } |
479 | ||
480 | /** |
|
481 | * @see org.apache.turbine.services.localization.LocalizationService#getLocale(HttpServletRequest) |
|
482 | */ |
|
483 | public final Locale getLocale(HttpServletRequest req) |
|
484 | { |
|
485 | 0 | return getLocale(req.getHeader(ACCEPT_LANGUAGE)); |
486 | } |
|
487 | ||
488 | /** |
|
489 | * @see org.apache.turbine.services.localization.LocalizationService#getLocale(String) |
|
490 | */ |
|
491 | public Locale getLocale(String header) |
|
492 | { |
|
493 | 0 | if (!StringUtils.isEmpty(header)) |
494 | { |
|
495 | 0 | LocaleTokenizer tok = new LocaleTokenizer(header); |
496 | 0 | if (tok.hasNext()) |
497 | { |
|
498 | 0 | return (Locale) tok.next(); |
499 | } |
|
500 | } |
|
501 | ||
502 | // Couldn't parse locale. |
|
503 | 0 | return defaultLocale; |
504 | } |
|
505 | ||
506 | /** |
|
507 | * @exception MissingResourceException Specified key cannot be matched. |
|
508 | * @see org.apache.turbine.services.localization.LocalizationService#getString(String, Locale, String) |
|
509 | */ |
|
510 | public String getString(String bundleName, Locale locale, String key) |
|
511 | { |
|
512 | 0 | String value = null; |
513 | ||
514 | 0 | if (locale == null) |
515 | { |
|
516 | 0 | locale = getLocale((String) null); |
517 | } |
|
518 | ||
519 | // Look for text in requested bundle. |
|
520 | 0 | ResourceBundle rb = getBundle(bundleName, locale); |
521 | 0 | value = getStringOrNull(rb, key); |
522 | ||
523 | // Look for text in list of default bundles. |
|
524 | 0 | if (value == null && bundleNames.length > 0) |
525 | { |
|
526 | String name; |
|
527 | 0 | for (int i = 0; i < bundleNames.length; i++) |
528 | { |
|
529 | 0 | name = bundleNames[i]; |
530 | //System.out.println("getString(): name=" + name + |
|
531 | // ", locale=" + locale + ", i=" + i); |
|
532 | 0 | if (!name.equals(bundleName)) |
533 | { |
|
534 | 0 | rb = getBundle(name, locale); |
535 | 0 | value = getStringOrNull(rb, key); |
536 | 0 | if (value != null) |
537 | { |
|
538 | 0 | locale = rb.getLocale(); |
539 | 0 | break; |
540 | } |
|
541 | } |
|
542 | } |
|
543 | } |
|
544 | ||
545 | 0 | if (value == null) |
546 | { |
|
547 | 0 | String loc = locale.toString(); |
548 | 0 | String mesg = LocalizationService.SERVICE_NAME + |
549 | " noticed missing resource: " + |
|
550 | "bundleName=" + bundleName + ", locale=" + loc + |
|
551 | ", key=" + key; |
|
552 | 0 | log.debug(mesg); |
553 | // Text not found in requested or default bundles. |
|
554 | 0 | throw new MissingResourceException(mesg, bundleName, key); |
555 | } |
|
556 | ||
557 | 0 | return value; |
558 | } |
|
559 | ||
560 | /** |
|
561 | * Gets localized text from a bundle if it's there. Otherwise, |
|
562 | * returns <code>null</code> (ignoring a possible |
|
563 | * <code>MissingResourceException</code>). |
|
564 | */ |
|
565 | protected final String getStringOrNull(ResourceBundle rb, String key) |
|
566 | { |
|
567 | 0 | if (rb != null) |
568 | { |
|
569 | try |
|
570 | { |
|
571 | 0 | return rb.getString(key); |
572 | } |
|
573 | 0 | catch (MissingResourceException ignored) |
574 | { |
|
575 | } |
|
576 | } |
|
577 | 0 | return null; |
578 | } |
|
579 | ||
580 | } |
This report is generated by jcoverage, Maven and Maven JCoverage Plugin. |