1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
31
32
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
309
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
321
322
323 public static void clearBundles() {
324 synchronized (bundleLock) {
325 languages.clear();
326 }
327 }
328
329
330
331
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 }