View Javadoc
1   /*
2    * This software was designed and created by Jason Carroll.
3    * Copyright (c) 2002, 2003, 2004 Jason Carroll.
4    * The author can be reached at jcarroll@cowsultants.com
5    * ITracker website: http://www.cowsultants.com
6    * ITracker forums: http://www.cowsultants.com/phpBB/index.php
7    *
8    * This program is free software; you can redistribute it and/or modify
9    * it only under the terms of the GNU General Public License as published by
10   * the Free Software Foundation; either version 2 of the License, or
11   * (at your option) any later version.
12   *
13   * This program is distributed in the hope that it will be useful,
14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   * GNU General Public License for more details.
17   */
18  
19  package org.itracker.core.resources;
20  
21  import org.apache.commons.collections.CollectionUtils;
22  import org.apache.commons.lang.StringUtils;
23  import org.itracker.ITrackerDirtyResourceException;
24  import org.slf4j.Logger;
25  import org.slf4j.LoggerFactory;
26  
27  import java.util.*;
28  
29  /**
30   * Please comment this class here. What is it for?
31   *
32   * @author ready
33   */
34  public class ITrackerResources {
35  
36      private static final Logger logger = LoggerFactory
37              .getLogger(ITrackerResources.class);
38  
39      public static final String RESOURCE_BUNDLE_NAME = "org.itracker.core.resources.ITracker";
40  
41      public static final String DEFAULT_LOCALE = "en_US";
42  
43      public static final String BASE_LOCALE = "BASE";
44  
45      public static final String KEY_BASE_CUSTOMFIELD_TYPE = "itracker.web.generic.";
46  
47      public static final String KEY_BASE_WORKFLOW_EVENT = "itracker.workflow.field.event.";
48  
49      public static final String KEY_BASE_PROJECT_STATUS = "itracker.project.status.";
50  
51      public static final String KEY_BASE_PERMISSION = "itracker.user.permission.";
52  
53      public static final String KEY_BASE_PRIORITY = "itracker.script.priority.";
54  
55      public static final String KEY_BASE_PRIORITY_LABEL = ".label";
56  
57      public static final String KEY_BASE_PRIORITY_SIZE = "size";
58  
59      public static final String KEY_BASE_RESOLUTION = "itracker.resolution.";
60  
61      public static final String KEY_BASE_ISSUE_RELATION = "itracker.issuerelation.";
62  
63      public static final String KEY_BASE_SEVERITY = "itracker.severity.";
64  
65      public static final String KEY_BASE_STATUS = "itracker.status.";
66  
67      public static final String KEY_BASE_USER_STATUS = "itracker.user.status.";
68  
69      public static final String KEY_BASE_CUSTOMFIELD = "itracker.customfield.";
70  
71      public static final String KEY_BASE_CUSTOMFIELD_OPTION = ".option.";
72  
73      public static final String KEY_BASE_CUSTOMFIELD_LABEL = ".label";
74  
75      public static final String KEY_BASE_LOCALE_NAME = "itracker.locale.name";
76  
77      private static String defaultLocale = DEFAULT_LOCALE;
78  
79      private static HashMap<String, Locale> locales = new HashMap<String, Locale>();
80  
81      private static HashMap<Locale, ResourceBundle> languages = new HashMap<Locale, ResourceBundle>();
82  
83      private static ITrackerResourcesProvider configurationService;
84      private static Collection<String> availableLocales;
85  
86      private static boolean initialized = false;
87  
88      private static final Object bundleLock = new Object();
89  
90  
91      public static Locale getLocale() {
92          return getLocale(getDefaultLocale());
93      }
94  
95      public static Locale getLocale(String localeString) {
96  
97          if (localeString == null || localeString.trim().equals("")) {
98              return getLocale(getDefaultLocale());
99          }
100 
101         Locale locale = locales.get(localeString);
102         if (locale == null
103                 && !StringUtils.isEmpty(localeString)) {
104             try {
105                 if (logger.isDebugEnabled()) {
106                     logger.debug("Creating new locale for '" + localeString
107                             + "'");
108                 }
109                 if (localeString.length() == 5) {
110                     locale = new Locale(localeString.substring(0, 2),
111                             localeString.substring(3));
112                 } else if (localeString.length() == 2) {
113                     locale = new Locale(localeString, "");
114                 } else if (localeString.equals(BASE_LOCALE)) {
115                     locale = new Locale("", "");
116                 } else {
117 
118                     logger
119                             .error("Invalid locale '"
120                                     + localeString
121                                     + "' specified.  It must be either LN or LN_CN.");
122                     throw new Exception("Invalid locale string");
123                 }
124             } catch (Exception ex) {
125                 if (!localeString.equals(getDefaultLocale())) {
126                     logger.error("Failed creating new locale for '"
127                             + localeString
128                             + "' attempting for default locale '"
129                             + getDefaultLocale() + "'", ex);
130                     return getLocale(getDefaultLocale());
131                 } else {
132                     logger.error("Failed creating new default locale for '"
133                             + getDefaultLocale()
134                             + "' attempting for DEFAULT_LOCALE '"
135                             + DEFAULT_LOCALE + "'", ex);
136                     return getLocale(DEFAULT_LOCALE);
137                 }
138             }
139             locales.put(localeString, locale);
140         }
141         return locale;
142     }
143 
144     public static String getDefaultLocale() {
145         return (defaultLocale == null ? DEFAULT_LOCALE : defaultLocale);
146     }
147 
148     private static void setDefaultLocale(String value) {
149         defaultLocale = value;
150     }
151 
152     public static String getLocaleDN(String locale, Locale displayLocale) {
153         String name;
154         if (null == displayLocale) {
155             displayLocale = getLocale();
156         }
157         try {
158             name = getBundle(displayLocale).getString(
159                     KEY_BASE_LOCALE_NAME + "." + locale);
160         } catch (RuntimeException e) {
161             name = getLocaleNativeName(getLocale(locale));
162         }
163 
164         return name;
165     }
166 
167     public static String getLocaleDN(Locale locale, Locale displayLocale) {
168 
169         if (null == displayLocale) {
170             return getLocaleNativeName(displayLocale);
171         }
172         return getLocaleDN(locale.toString(), displayLocale);
173 
174     }
175 
176     public static String getLocaleFullDN(Locale locale, Locale displayLocale) {
177 
178         if (null == locale) {
179             locale = new Locale("");
180         }
181         String fullName = StringUtils.trimToNull(getLocaleNativeName(locale));
182         if (null == displayLocale || locale.getLanguage().equals(displayLocale.getLanguage())) {
183             return fullName;
184         }
185         if (StringUtils.equals(fullName, String.valueOf(locale))) {
186             fullName = getLocaleDN(locale, displayLocale);
187             return fullName;
188         } else {
189             String localizedName = StringUtils.trimToNull(getLocaleDN(locale, displayLocale));
190             if (null != fullName && !StringUtils.equals(fullName, localizedName)) {
191                 return fullName.trim() + " (" + localizedName.trim() + ")";
192             } else if (null != localizedName) {
193                 return localizedName.trim();
194             } else if (null != fullName) {
195                 return fullName.trim();
196             }
197         }
198 
199 
200         return locale.getDisplayName()
201                 + (!locale.equals(displayLocale) ? " (" + locale.getDisplayLanguage(locale) + ")" : "");
202 
203     }
204 
205     public static String getLocaleNativeName(Locale locale) {
206         try {
207             return getString(KEY_BASE_LOCALE_NAME, locale);
208         } catch (MissingResourceException e) {
209             return locale.getDisplayName(locale);
210         }
211     }
212 
213     public static Map<String, String> getLocaleNamesMap(Locale locale, Set<String> languageCodes, Map<String, List<String>> languagesMap) {
214         Map<String, String> ret = new LinkedHashMap<String, String>();
215         for (String languageCode : languageCodes) {
216             List<String> languagelist = languagesMap.get(languageCode);
217 
218             String name = getLocaleFullDN(ITrackerResources.getLocale(languageCode), locale);
219 
220             ret.put(languageCode, name);
221             for (String languageitem : languagelist) {
222                 name = getLocaleFullDN(ITrackerResources.getLocale(languageitem), locale);
223                 ret.put(languageitem, name);
224             }
225 
226         }
227         if (ret.size() == 0) {
228             ret.put(getDefaultLocale(), getLocaleNativeName(getLocale(getDefaultLocale())));
229         }
230         return ret;
231 
232     }
233 
234     public static ResourceBundle getBundle() {
235         return getBundle(getDefaultLocale());
236     }
237 
238     public static ResourceBundle getBundle(String locale) {
239         if (locale == null || locale.equals("")) {
240             locale = getDefaultLocale();
241         }
242 
243         return getBundle(getLocale(locale));
244     }
245 
246     public static ResourceBundle getBundle(Locale locale) {
247         if (locale == null) {
248             locale = getLocale();
249         }
250 
251         ResourceBundle bundle = languages.get(locale);
252         if (bundle == null) {
253             if (logger.isDebugEnabled()) {
254                 logger.debug("getBundle: Loading new resource bundle for locale " + locale
255                         + " from the database.");
256             }
257             if (!isInitialized()) {
258                 return ITrackerResourceBundle.loadBundle(locale);
259             } else {
260 
261                 Properties languageItems = configurationService
262                         .getLanguageProperties(locale);
263 
264                 logger.info("lazy loading locale bundle resources: {}", locale);
265 
266                 bundle = ITrackerResourceBundle.loadBundle(locale, languageItems);
267                 logger.info("getBundle: got loaded for locale {} with {} items from the database.", locale, null == languageItems ? "no" : CollectionUtils.size(languageItems));
268                 logger.debug("getBundle: got loaded for locale {} with items {} from the database.", locale, languageItems);
269 
270                 putBundle(locale, bundle);
271             }
272 
273         }
274 
275         return bundle;
276     }
277 
278     public static ResourceBundle getEditBundle(Locale locale) {
279         if (locale == null) {
280             locale = getLocale(getDefaultLocale());
281         }
282         ResourceBundle bundle;
283         logger.debug("Loading new resource bundle for locale " + locale
284                 + " from the database.");
285         Properties languageItems = configurationService.getLanguageProperties(
286                 locale);
287         bundle = ITrackerResourceBundle.loadBundle(locale, languageItems);
288         putBundle(locale, bundle);
289         return bundle;
290     }
291 
292     public static void putBundle(Locale locale, ResourceBundle bundle) {
293         if (locale != null && bundle != null) {
294             synchronized (bundleLock) {
295                 languages.put(locale, bundle);
296                 String localeString = locale.toString();
297                 if (localeString.length() == 5) {
298                     localeString = localeString.substring(0, 2) + "_"
299                             + localeString.substring(3).toUpperCase();
300                 }
301                 locales.put(localeString, locale);
302             }
303         }
304     }
305 
306 
307     /**
308      * Clears a single cached resource bundle. The next time the bundle is
309      * accessed, it will be reloaded and placed into the cache.
310      */
311     public static void clearBundle(Locale locale) {
312         if (locale != null) {
313             synchronized (bundleLock) {
314                 languages.remove(locale);
315             }
316         }
317     }
318 
319     /**
320      * Clears all cached resource bundles. The next time a bundle is accessed,
321      * it will be reloaded and placed into the cache.
322      */
323     public static void clearBundles() {
324         synchronized (bundleLock) {
325             languages.clear();
326         }
327     }
328 
329     /**
330      * Clears a single key from all cached resource bundles. The key is then
331      * marked that it is dirty and should be reloaded on hte next access.
332      */
333     public static void clearKeyFromBundles(String key, boolean markDirty) {
334         if (key != null) {
335             synchronized (bundleLock) {
336                 for (ResourceBundle resourceBundle : languages.values()) {
337                     ((ITrackerResourceBundle) resourceBundle).removeValue(key,
338                             markDirty);
339                 }
340             }
341         }
342     }
343 
344     public static String getString(String key) {
345         return getString(key, getLocale(defaultLocale));
346     }
347 
348     public static String getString(String key, String locale) {
349         if (key == null) {
350             return "";
351         }
352 
353         if (locale == null || locale.equals("")) {
354             locale = getDefaultLocale();
355         }
356 
357         return getString(key, getLocale(locale));
358     }
359 
360     private static String handleMissingResourceException(final MissingResourceException ex, final String key, final Locale locale) {
361 
362         logger.warn(
363                 "no value while retrieving translation key '{}' for locale {}", key, locale);
364         Locale l = locale;
365         if (null == l) {
366             l = getLocale(getDefaultLocale());
367         }
368         if (StringUtils.isNotEmpty(l.getCountry())) {
369             l = new Locale(l.getLanguage());
370         } else if (StringUtils.isNotEmpty(l.getLanguage())) {
371             l = new Locale("");
372         }
373         if (l != locale) {
374             logger.debug("resolving {} from parent bundle ()", key, l);
375             return getString(key, l);
376         }
377         throw ex;
378 
379     }
380     private static String handleDirtyResourceException(final ITrackerDirtyResourceException e, final String key, final Locale locale) {
381 
382         logger.debug(
383                 "handleDirtyResourceException: key '{}' for locale {}", new Object[]{key, locale, e});
384         ITrackerResourceBundleitracker/core/resources/ITrackerResourceBundle.html#ITrackerResourceBundle">ITrackerResourceBundle bundle = (ITrackerResourceBundle)getBundle(locale);
385         try {
386             final String languageItem = configurationService
387                     .getLanguageEntry(key, locale);
388             bundle.updateValue(key, languageItem);
389             return languageItem;
390         } catch (MissingResourceException e2) {
391             bundle.removeValue(key, false);
392             try {
393                 return bundle.getString(key);
394             } catch (MissingResourceException e3) {
395                 return handleMissingResourceException(e2, key, locale);
396             }
397         }
398     }
399 
400     public static String getString(final String key, final Locale locale) {
401         if (key == null) {
402             return "";
403         }
404 
405         String val;
406         try {
407             final ResourceBundle bundle = getBundle(locale);
408             try {
409                 val = bundle.getString(key);
410                 return val;
411 
412             } catch (ITrackerDirtyResourceException e) {
413                 val = handleDirtyResourceException(e, key, locale);
414             } catch (MissingResourceException e) {
415                 val = handleMissingResourceException(e, key, locale);
416             }
417             return val;
418         } catch (NullPointerException ex) {
419             logger.error(
420                     "Unable to get any resources.  The requested locale was "
421                             + locale, ex);
422             return "MISSING BUNDLE: " + locale;
423 
424         } catch (MissingResourceException ex) {
425             logger.warn(
426                     "MissingResourceException caught while retrieving translation key '{}' for locale {}", key, locale);
427             logger.debug(
428                     "MissingResourceException was", ex);
429             return "MISSING KEY: " + key;
430         } catch (RuntimeException ex) {
431             logger.info("getString: not found " + key + " locale: " + locale,
432                     ex);
433             try {
434                 return getEditBundle(locale).getString(key);
435             } catch (Exception ex2) {
436                 logger.warn(
437                         "Exception caught while retrieving translation key '{}' for locale {}: {}", new Object[]{key, locale, ex2.getMessage()});
438                 logger.debug("Exception was", ex2);
439                 return "MISSING KEY: " + key;
440             }
441         }
442     }
443 
444     public static String getString(String key, Object[] options) {
445         return getString(key, getLocale(getDefaultLocale()), options);
446     }
447 
448     public static String getString(String key, String locale, Object[] options) {
449         return getString(key, getLocale(locale), options);
450     }
451 
452     public static String getString(String key, Locale locale, Object[] options) {
453         String message = getString(key, locale);
454         return MessageFormat.format(message, options, locale);
455     }
456 
457     public static String getString(String key, String locale, String option) {
458         String message = getString(key, locale);
459         return MessageFormat.format(message, new Object[]{option},
460                 getLocale(locale));
461     }
462 
463     public static String getString(String key, Locale locale, String option) {
464         String message = getString(key, locale);
465         return MessageFormat.format(message, new Object[]{option}, locale);
466     }
467 
468     public static String getCheckForKey(String key)
469             throws MissingResourceException {
470         return getCheckForKey(key, getLocale());
471     }
472 
473     public static String getCheckForKey(String key, Locale locale)
474             throws MissingResourceException {
475         try {
476             return getBundle(locale).getString(key);
477         } catch (ITrackerDirtyResourceException idre) {
478             return getString(key, locale);
479         } catch (NullPointerException ex) {
480             logger.debug("Unable to get ResourceBundle for locale " + locale,
481                     ex);
482             throw new MissingResourceException("MISSING LOCALE: " + locale,
483                     "ITrackerResources", key);
484         }
485     }
486 
487     public static boolean isLongString(String key) {
488         String value = getString(key);
489         return value.length() > 80 || value.indexOf('\n') > 0;
490     }
491 
492     public static String escapeUnicodeString(String str, boolean escapeAll) {
493         if (str == null) {
494             return "";
495         }
496 
497         StringBuilder sb = new StringBuilder();
498         for (int i = 0; i < str.length(); i++) {
499             char ch = str.charAt(i);
500             if (!escapeAll && ((ch >= 0x0020) && (ch <= 0x007e))) {
501                 sb.append(ch);
502             } else {
503                 sb.append('\\').append('u');
504                 sb.append(encodeHex((ch >> 12) & 0xF));
505                 sb.append(encodeHex((ch >> 8) & 0xF));
506                 sb.append(encodeHex((ch >> 4) & 0xF));
507                 sb.append(encodeHex(ch & 0xF));
508             }
509         }
510         return sb.toString();
511     }
512 
513     public static String unescapeUnicodeString(String str) {
514         final StringBuilder sb = new StringBuilder();
515 
516         for (int i = 0; i < str.length(); ) {
517             char ch = str.charAt(i++);
518             if (ch == '\\') {
519                 if (str.charAt(i++) == 'u') {
520                     int value = 0;
521                     for (int j = 0; j < 4; j++) {
522                         value = (value << 4) + decodeHex(str.charAt(i++));
523                     }
524                     sb.append((char) value);
525                 } else {
526                     sb.append("\\").append(str.charAt(i));
527                 }
528             } else {
529                 sb.append(ch);
530             }
531         }
532         return sb.toString();
533     }
534 
535     public static final String HEXCHARS = "0123456789ABCDEF";
536 
537     public static char encodeHex(int value) {
538         return HEXCHARS.charAt(value & 0xf);
539     }
540 
541     public static int decodeHex(char ch) {
542         int value = -1;
543 
544         if (ch >= '0' && ch <= '9') {
545             value = ch - '0';
546         } else if (ch >= 'a' && ch <= 'f') {
547             value = ch - 'a' + 10;
548         } else if (ch >= 'A' && ch <= 'F') {
549             value = ch - 'A' + 10;
550         }
551 
552         return value;
553     }
554 
555     public static boolean isInitialized() {
556         return initialized;
557     }
558 
559     private static void setInitialized(boolean initialized) {
560         ITrackerResources.initialized = initialized;
561     }
562 
563     public static void setConfigurationService(ITrackerResourcesProvider service) {
564         if (isInitialized()) {
565             throw new IllegalStateException("Service is already set up.");
566         }
567         configurationService = service;
568         String[] availableLocales = StringUtils.split(configurationService.getProperty("available_locales", getDefaultLocale()), ',');
569         ArrayList<String> locales = new ArrayList<>(availableLocales.length);
570         for (String l: availableLocales) {
571             locales.add(StringUtils.trim(l));
572         }
573         ITrackerResources.availableLocales = locales;
574         ITrackerResources.setDefaultLocale(configurationService.getProperty("default_locale", ITrackerResources.DEFAULT_LOCALE));
575 
576         logger.info("Set system default locale to '" + ITrackerResources.getDefaultLocale() + "'");
577         setInitialized(true);
578 
579     }
580 
581 }