1   /**
2    * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
3    *
4    * This library is free software; you can redistribute it and/or modify it under
5    * the terms of the GNU Lesser General Public License as published by the Free
6    * Software Foundation; either version 2.1 of the License, or (at your option)
7    * any later version.
8    *
9    * This library is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11   * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12   * details.
13   */
14  
15  package com.liferay.util;
16  
17  import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
18  import com.liferay.portal.kernel.io.unsync.UnsyncStringWriter;
19  import com.liferay.portal.kernel.json.JSONObject;
20  import com.liferay.portal.kernel.language.LanguageUtil;
21  import com.liferay.portal.kernel.log.Log;
22  import com.liferay.portal.kernel.log.LogFactoryUtil;
23  import com.liferay.portal.kernel.util.LocaleUtil;
24  import com.liferay.portal.kernel.util.ParamUtil;
25  import com.liferay.portal.kernel.util.PortalClassLoaderUtil;
26  import com.liferay.portal.kernel.util.StringPool;
27  import com.liferay.portal.kernel.util.StringUtil;
28  import com.liferay.portal.kernel.util.Tuple;
29  import com.liferay.portal.kernel.util.Validator;
30  
31  import java.util.HashMap;
32  import java.util.Locale;
33  import java.util.Map;
34  
35  import javax.portlet.ActionRequest;
36  import javax.portlet.PortletPreferences;
37  import javax.portlet.PortletRequest;
38  
39  import javax.xml.stream.XMLInputFactory;
40  import javax.xml.stream.XMLOutputFactory;
41  import javax.xml.stream.XMLStreamConstants;
42  import javax.xml.stream.XMLStreamException;
43  import javax.xml.stream.XMLStreamReader;
44  import javax.xml.stream.XMLStreamWriter;
45  
46  import org.apache.commons.collections.map.ReferenceMap;
47  
48  /**
49   * <a href="LocalizationUtil.java.html"><b><i>View Source</i></b></a>
50   *
51   * <p>
52   * This class is used to localize values stored in XML and is often used to add
53   * localization behavior to value objects.
54   * </p>
55   *
56   * <p>
57   * Caching of the localized values is done in this class rather than in the
58   * value object since value objects get flushed from cache fairly quickly.
59   * Though lookups performed on a key based on an XML file is slower than lookups
60   * done at the value object level in general, the value object will get flushed
61   * at a rate which works against the performance gain. The cache is a soft hash
62   * map which prevents memory leaks within the system while enabling the cache to
63   * live longer than in a weak hash map.
64   * </p>
65   *
66   * @author Alexander Chow
67   * @author Jorge Ferrer
68   * @author Mauro Mariuzzo
69   * @author Julio Camarero
70   */
71  public class LocalizationUtil {
72  
73      public static Object deserialize(JSONObject jsonObject) {
74          Locale[] locales = LanguageUtil.getAvailableLocales();
75  
76          Map<Locale, String> map = new HashMap<Locale, String>();
77  
78          for (Locale locale : locales) {
79              String languageId = LocaleUtil.toLanguageId(locale);
80  
81              String value = jsonObject.getString(languageId);
82  
83              if (Validator.isNotNull(value)) {
84                  map.put(locale, value);
85              }
86          }
87  
88          return map;
89      }
90  
91      public static String[] getAvailableLocales(String xml) {
92          String attributeValue = _getRootAttribute(
93              xml, _AVAILABLE_LOCALES, StringPool.BLANK);
94  
95          return StringUtil.split(attributeValue);
96      }
97  
98      public static String getDefaultLocale(String xml) {
99          String defaultLanguageId = LocaleUtil.toLanguageId(
100             LocaleUtil.getDefault());
101 
102         return _getRootAttribute(xml, _DEFAULT_LOCALE, defaultLanguageId);
103     }
104 
105     public static String getLocalization(
106         String xml, String requestedLanguageId) {
107 
108         return getLocalization(xml, requestedLanguageId, true);
109     }
110 
111     public static String getLocalization(
112         String xml, String requestedLanguageId, boolean useDefault) {
113 
114         String value = _getCachedValue(xml, requestedLanguageId, useDefault);
115 
116         if (value != null) {
117             return value;
118         }
119         else {
120             value = StringPool.BLANK;
121         }
122 
123         String systemDefaultLanguageId = LocaleUtil.toLanguageId(
124             LocaleUtil.getDefault());
125 
126         String defaultValue = StringPool.BLANK;
127 
128         if (!Validator.isXml(xml)) {
129             if (requestedLanguageId.equals(systemDefaultLanguageId)) {
130                 value = xml;
131             }
132             else {
133                 value = defaultValue;
134             }
135 
136             _setCachedValue(xml, requestedLanguageId, useDefault, value);
137 
138             return value;
139         }
140 
141         XMLStreamReader xmlStreamReader = null;
142 
143         ClassLoader portalClassLoader = PortalClassLoaderUtil.getClassLoader();
144 
145         Thread currentThread = Thread.currentThread();
146 
147         ClassLoader contextClassLoader = currentThread.getContextClassLoader();
148 
149         try {
150             if (contextClassLoader != portalClassLoader) {
151                 currentThread.setContextClassLoader(portalClassLoader);
152             }
153 
154             XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
155 
156             xmlStreamReader = xmlInputFactory.createXMLStreamReader(
157                 new UnsyncStringReader(xml));
158 
159             String defaultLanguageId = StringPool.BLANK;
160 
161             // Skip root node
162 
163             if (xmlStreamReader.hasNext()) {
164                 xmlStreamReader.nextTag();
165 
166                 defaultLanguageId = xmlStreamReader.getAttributeValue(
167                     null, _DEFAULT_LOCALE);
168 
169                 if (Validator.isNull(defaultLanguageId)) {
170                     defaultLanguageId = systemDefaultLanguageId;
171                 }
172             }
173 
174             // Find specified language and/or default language
175 
176             while (xmlStreamReader.hasNext()) {
177                 int event = xmlStreamReader.next();
178 
179                 if (event == XMLStreamConstants.START_ELEMENT) {
180                     String languageId = xmlStreamReader.getAttributeValue(
181                         null, _LANGUAGE_ID);
182 
183                     if (Validator.isNull(languageId)) {
184                         languageId = defaultLanguageId;
185                     }
186 
187                     if (languageId.equals(defaultLanguageId) ||
188                         languageId.equals(requestedLanguageId)) {
189 
190                         while (xmlStreamReader.hasNext()) {
191                             event = xmlStreamReader.next();
192 
193                             if (event == XMLStreamConstants.CHARACTERS ||
194                                 event == XMLStreamConstants.CDATA) {
195 
196                                 String text = xmlStreamReader.getText();
197 
198                                 if (languageId.equals(defaultLanguageId)) {
199                                     defaultValue = text;
200                                 }
201 
202                                 if (languageId.equals(requestedLanguageId)) {
203                                     value = text;
204                                 }
205 
206                                 break;
207                             }
208                             else if (event == XMLStreamConstants.END_ELEMENT) {
209                                 break;
210                             }
211                         }
212 
213                         if (Validator.isNotNull(value)) {
214                             break;
215                         }
216                     }
217                 }
218                 else if (event == XMLStreamConstants.END_DOCUMENT) {
219                     break;
220                 }
221             }
222 
223             if (useDefault && Validator.isNull(value)) {
224                 value = defaultValue;
225             }
226         }
227         catch (Exception e) {
228             if (_log.isWarnEnabled()) {
229                 _log.warn(e, e);
230             }
231         }
232         finally {
233             if (contextClassLoader != portalClassLoader) {
234                 currentThread.setContextClassLoader(contextClassLoader);
235             }
236 
237             if (xmlStreamReader != null) {
238                 try {
239                     xmlStreamReader.close();
240                 }
241                 catch (Exception e) {
242                 }
243             }
244         }
245 
246         _setCachedValue(xml, requestedLanguageId, useDefault, value);
247 
248         return value;
249     }
250 
251     public static Map<Locale, String> getLocalizationMap(
252         PortletRequest portletRequest, String parameter) {
253 
254         Locale[] locales = LanguageUtil.getAvailableLocales();
255 
256         Map<Locale, String> map = new HashMap<Locale, String>();
257 
258         for (Locale locale : locales) {
259             String languageId = LocaleUtil.toLanguageId(locale);
260 
261             String localeParameter =
262                 parameter + StringPool.UNDERLINE + languageId;
263 
264             map.put(
265                 locale, ParamUtil.getString(portletRequest, localeParameter));
266         }
267 
268         return map;
269     }
270 
271     public static Map<Locale, String> getLocalizationMap(String xml) {
272         Locale[] locales = LanguageUtil.getAvailableLocales();
273 
274         Map<Locale, String> map = new HashMap<Locale, String>();
275 
276         for (Locale locale : locales) {
277             String languageId = LocaleUtil.toLanguageId(locale);
278 
279             map.put(locale, getLocalization(xml, languageId));
280         }
281 
282         return map;
283     }
284 
285     /**
286      * @deprecated Use <code>getLocalizationMap</code>.
287      */
288     public static Map<Locale, String> getLocalizedParameter(
289         PortletRequest portletRequest, String parameter) {
290 
291         return getLocalizationMap(portletRequest, parameter);
292     }
293 
294     public static String getPreferencesValue(
295         PortletPreferences preferences, String key, String languageId) {
296 
297         return getPreferencesValue(preferences, key, languageId, true);
298     }
299 
300     public static String getPreferencesValue(
301         PortletPreferences preferences, String key, String languageId,
302         boolean useDefault) {
303 
304         String localizedKey = _getPreferencesKey(key, languageId);
305 
306         String value = preferences.getValue(localizedKey, StringPool.BLANK);
307 
308         if (useDefault && Validator.isNull(value)) {
309             value = preferences.getValue(key, StringPool.BLANK);
310         }
311 
312         return value;
313     }
314 
315     public static String[] getPreferencesValues(
316         PortletPreferences preferences, String key, String languageId) {
317 
318         return getPreferencesValues(preferences, key, languageId, true);
319     }
320 
321     public static String[] getPreferencesValues(
322         PortletPreferences preferences, String key, String languageId,
323         boolean useDefault) {
324 
325         String localizedKey = _getPreferencesKey(key, languageId);
326 
327         String[] values = preferences.getValues(localizedKey, new String[0]);
328 
329         if (useDefault && Validator.isNull(values)) {
330             values = preferences.getValues(key, new String[0]);
331         }
332 
333         return values;
334     }
335 
336     public static String removeLocalization(
337         String xml, String key, String requestedLanguageId) {
338 
339         return removeLocalization(xml, key, requestedLanguageId, false);
340     }
341 
342     public static String removeLocalization(
343         String xml, String key, String requestedLanguageId, boolean cdata) {
344 
345         if (Validator.isNull(xml)) {
346             return StringPool.BLANK;
347         }
348 
349         xml = _sanitizeXML(xml);
350 
351         String systemDefaultLanguageId = LocaleUtil.toLanguageId(
352             LocaleUtil.getDefault());
353 
354         XMLStreamReader xmlStreamReader = null;
355         XMLStreamWriter xmlStreamWriter = null;
356 
357         ClassLoader portalClassLoader = PortalClassLoaderUtil.getClassLoader();
358 
359         Thread currentThread = Thread.currentThread();
360 
361         ClassLoader contextClassLoader = currentThread.getContextClassLoader();
362 
363         try {
364             if (contextClassLoader != portalClassLoader) {
365                 currentThread.setContextClassLoader(portalClassLoader);
366             }
367 
368             XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
369 
370             xmlStreamReader = xmlInputFactory.createXMLStreamReader(
371                 new UnsyncStringReader(xml));
372 
373             String availableLocales = StringPool.BLANK;
374             String defaultLanguageId = StringPool.BLANK;
375 
376             // Read root node
377 
378             if (xmlStreamReader.hasNext()) {
379                 xmlStreamReader.nextTag();
380 
381                 availableLocales = xmlStreamReader.getAttributeValue(
382                     null, _AVAILABLE_LOCALES);
383                 defaultLanguageId = xmlStreamReader.getAttributeValue(
384                     null, _DEFAULT_LOCALE);
385 
386                 if (Validator.isNull(defaultLanguageId)) {
387                     defaultLanguageId = systemDefaultLanguageId;
388                 }
389             }
390 
391             if ((availableLocales != null) &&
392                 (availableLocales.indexOf(requestedLanguageId) != -1)) {
393 
394                 availableLocales = StringUtil.remove(
395                     availableLocales, requestedLanguageId, StringPool.COMMA);
396 
397                 UnsyncStringWriter unsyncStringWriter = new UnsyncStringWriter(
398                     true);
399 
400                 XMLOutputFactory xmlOutputFactory =
401                     XMLOutputFactory.newInstance();
402 
403                 xmlStreamWriter = xmlOutputFactory.createXMLStreamWriter(
404                     unsyncStringWriter);
405 
406                 xmlStreamWriter.writeStartDocument();
407                 xmlStreamWriter.writeStartElement(_ROOT);
408                 xmlStreamWriter.writeAttribute(
409                     _AVAILABLE_LOCALES, availableLocales);
410                 xmlStreamWriter.writeAttribute(
411                     _DEFAULT_LOCALE, defaultLanguageId);
412 
413                 _copyNonExempt(
414                     xmlStreamReader, xmlStreamWriter, requestedLanguageId,
415                     defaultLanguageId, cdata);
416 
417                 xmlStreamWriter.writeEndElement();
418                 xmlStreamWriter.writeEndDocument();
419 
420                 xmlStreamWriter.close();
421                 xmlStreamWriter = null;
422 
423                 xml = unsyncStringWriter.toString();
424             }
425         }
426         catch (Exception e) {
427             if (_log.isWarnEnabled()) {
428                 _log.warn(e, e);
429             }
430         }
431         finally {
432             if (contextClassLoader != portalClassLoader) {
433                 currentThread.setContextClassLoader(contextClassLoader);
434             }
435 
436             if (xmlStreamReader != null) {
437                 try {
438                     xmlStreamReader.close();
439                 }
440                 catch (Exception e) {
441                 }
442             }
443 
444             if (xmlStreamWriter != null) {
445                 try {
446                     xmlStreamWriter.close();
447                 }
448                 catch (Exception e) {
449                 }
450             }
451         }
452 
453         return xml;
454     }
455 
456     public static void setLocalizedPreferencesValues (
457             ActionRequest actionRequest, PortletPreferences preferences,
458             String parameter)
459         throws Exception {
460 
461         Map<Locale, String> map = getLocalizedParameter(
462             actionRequest, parameter);
463 
464         for (Locale locale : map.keySet()) {
465             String languageId = LocaleUtil.toLanguageId(locale);
466 
467             String key = parameter + StringPool.UNDERLINE + languageId;
468             String value = map.get(locale);
469 
470             preferences.setValue(key, value);
471         }
472     }
473 
474     public static void setPreferencesValue(
475             PortletPreferences preferences, String key, String languageId,
476             String value)
477         throws Exception {
478 
479         preferences.setValue(_getPreferencesKey(key, languageId), value);
480     }
481 
482     public static void setPreferencesValues(
483             PortletPreferences preferences, String key, String languageId,
484             String[] values)
485         throws Exception {
486 
487         preferences.setValues(_getPreferencesKey(key, languageId), values);
488     }
489 
490     public static String updateLocalization(
491         String xml, String key, String value) {
492 
493         String defaultLanguageId = LocaleUtil.toLanguageId(
494             LocaleUtil.getDefault());
495 
496         return updateLocalization(
497             xml, key, value, defaultLanguageId, defaultLanguageId);
498     }
499 
500     public static String updateLocalization(
501         String xml, String key, String value, String requestedLanguageId) {
502 
503         String defaultLanguageId = LocaleUtil.toLanguageId(
504             LocaleUtil.getDefault());
505 
506         return updateLocalization(
507             xml, key, value, requestedLanguageId, defaultLanguageId);
508     }
509 
510     public static String updateLocalization(
511         String xml, String key, String value, String requestedLanguageId,
512         String defaultLanguageId) {
513 
514         return updateLocalization(
515             xml, key, value, requestedLanguageId, defaultLanguageId, false);
516     }
517 
518     public static String updateLocalization(
519         String xml, String key, String value, String requestedLanguageId,
520         String defaultLanguageId, boolean cdata) {
521 
522         xml = _sanitizeXML(xml);
523 
524         XMLStreamReader xmlStreamReader = null;
525         XMLStreamWriter xmlStreamWriter = null;
526 
527         ClassLoader portalClassLoader = PortalClassLoaderUtil.getClassLoader();
528 
529         Thread currentThread = Thread.currentThread();
530 
531         ClassLoader contextClassLoader = currentThread.getContextClassLoader();
532 
533         try {
534             if (contextClassLoader != portalClassLoader) {
535                 currentThread.setContextClassLoader(portalClassLoader);
536             }
537 
538             XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
539 
540             xmlStreamReader = xmlInputFactory.createXMLStreamReader(
541                 new UnsyncStringReader(xml));
542 
543             String availableLocales = StringPool.BLANK;
544 
545             // Read root node
546 
547             if (xmlStreamReader.hasNext()) {
548                 xmlStreamReader.nextTag();
549 
550                 availableLocales = xmlStreamReader.getAttributeValue(
551                     null, _AVAILABLE_LOCALES);
552 
553                 if (Validator.isNull(availableLocales)) {
554                     availableLocales = defaultLanguageId;
555                 }
556 
557                 if (availableLocales.indexOf(requestedLanguageId) == -1) {
558                     availableLocales = StringUtil.add(
559                         availableLocales, requestedLanguageId,
560                         StringPool.COMMA);
561                 }
562             }
563 
564             UnsyncStringWriter unsyncStringWriter = new UnsyncStringWriter(
565                 true);
566 
567             XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();
568 
569             xmlStreamWriter = xmlOutputFactory.createXMLStreamWriter(
570                 unsyncStringWriter);
571 
572             xmlStreamWriter.writeStartDocument();
573             xmlStreamWriter.writeStartElement(_ROOT);
574             xmlStreamWriter.writeAttribute(
575                 _AVAILABLE_LOCALES, availableLocales);
576             xmlStreamWriter.writeAttribute(_DEFAULT_LOCALE, defaultLanguageId);
577 
578             _copyNonExempt(
579                 xmlStreamReader, xmlStreamWriter, requestedLanguageId,
580                 defaultLanguageId, cdata);
581 
582             if (cdata) {
583                 xmlStreamWriter.writeStartElement(key);
584                 xmlStreamWriter.writeAttribute(
585                     _LANGUAGE_ID, requestedLanguageId);
586                 xmlStreamWriter.writeCData(value);
587                 xmlStreamWriter.writeEndElement();
588             }
589             else {
590                 xmlStreamWriter.writeStartElement(key);
591                 xmlStreamWriter.writeAttribute(
592                     _LANGUAGE_ID, requestedLanguageId);
593                 xmlStreamWriter.writeCharacters(value);
594                 xmlStreamWriter.writeEndElement();
595             }
596 
597             xmlStreamWriter.writeEndElement();
598             xmlStreamWriter.writeEndDocument();
599 
600             xmlStreamWriter.close();
601             xmlStreamWriter = null;
602 
603             xml = unsyncStringWriter.toString();
604         }
605         catch (Exception e) {
606             if (_log.isWarnEnabled()) {
607                 _log.warn(e, e);
608             }
609         }
610         finally {
611             if (contextClassLoader != portalClassLoader) {
612                 currentThread.setContextClassLoader(contextClassLoader);
613             }
614 
615             if (xmlStreamReader != null) {
616                 try {
617                     xmlStreamReader.close();
618                 }
619                 catch (Exception e) {
620                 }
621             }
622 
623             if (xmlStreamWriter != null) {
624                 try {
625                     xmlStreamWriter.close();
626                 }
627                 catch (Exception e) {
628                 }
629             }
630         }
631 
632         return xml;
633     }
634 
635     private static void _copyNonExempt(
636             XMLStreamReader xmlStreamReader, XMLStreamWriter xmlStreamWriter,
637             String exemptLanguageId, String defaultLanguageId, boolean cdata)
638         throws XMLStreamException {
639 
640         while (xmlStreamReader.hasNext()) {
641             int event = xmlStreamReader.next();
642 
643             if (event == XMLStreamConstants.START_ELEMENT) {
644                 String languageId = xmlStreamReader.getAttributeValue(
645                     null, _LANGUAGE_ID);
646 
647                 if (Validator.isNull(languageId)) {
648                     languageId = defaultLanguageId;
649                 }
650 
651                 if (!languageId.equals(exemptLanguageId)) {
652                     xmlStreamWriter.writeStartElement(
653                         xmlStreamReader.getLocalName());
654                     xmlStreamWriter.writeAttribute(_LANGUAGE_ID, languageId);
655 
656                     while (xmlStreamReader.hasNext()) {
657                         event = xmlStreamReader.next();
658 
659                         if (event == XMLStreamConstants.CHARACTERS ||
660                             event == XMLStreamConstants.CDATA) {
661 
662                             String text = xmlStreamReader.getText();
663 
664                             if (cdata) {
665                                 xmlStreamWriter.writeCData(text);
666                             }
667                             else {
668                                 xmlStreamWriter.writeCharacters(
669                                     xmlStreamReader.getText());
670                             }
671 
672                             break;
673                         }
674                         else if (event == XMLStreamConstants.END_ELEMENT) {
675                             break;
676                         }
677                     }
678 
679                     xmlStreamWriter.writeEndElement();
680                 }
681             }
682             else if (event == XMLStreamConstants.END_DOCUMENT) {
683                 break;
684             }
685         }
686     }
687 
688     private static String _getCachedValue(
689         String xml, String requestedLanguageId, boolean useDefault) {
690 
691         String value = null;
692 
693         Map<Tuple, String> valueMap = _cache.get(xml);
694 
695         if (valueMap != null) {
696             Tuple subkey = new Tuple(useDefault, requestedLanguageId);
697 
698             value = valueMap.get(subkey);
699         }
700 
701         return value;
702     }
703 
704     private static String _getPreferencesKey(String key, String languageId) {
705         String defaultLanguageId = LocaleUtil.toLanguageId(
706             LocaleUtil.getDefault());
707 
708         if (!languageId.equals(defaultLanguageId)) {
709             key += StringPool.UNDERLINE + languageId;
710         }
711 
712         return key;
713     }
714 
715     private static String _getRootAttribute(
716         String xml, String name, String defaultValue) {
717 
718         String value = null;
719 
720         XMLStreamReader xmlStreamReader = null;
721 
722         ClassLoader portalClassLoader = PortalClassLoaderUtil.getClassLoader();
723 
724         Thread currentThread = Thread.currentThread();
725 
726         ClassLoader contextClassLoader = currentThread.getContextClassLoader();
727 
728         try {
729             if (contextClassLoader != portalClassLoader) {
730                 currentThread.setContextClassLoader(portalClassLoader);
731             }
732 
733             XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
734 
735             xmlStreamReader = xmlInputFactory.createXMLStreamReader(
736                 new UnsyncStringReader(xml));
737 
738             if (xmlStreamReader.hasNext()) {
739                 xmlStreamReader.nextTag();
740 
741                 value = xmlStreamReader.getAttributeValue(null, name);
742             }
743         }
744         catch (Exception e) {
745             if (_log.isWarnEnabled()) {
746                 _log.warn(e, e);
747             }
748         }
749         finally {
750             if (contextClassLoader != portalClassLoader) {
751                 currentThread.setContextClassLoader(contextClassLoader);
752             }
753 
754             if (xmlStreamReader != null) {
755                 try {
756                     xmlStreamReader.close();
757                 }
758                 catch (Exception e) {
759                 }
760             }
761         }
762 
763         if (Validator.isNull(value)) {
764             value = defaultValue;
765         }
766 
767         return value;
768     }
769 
770     private static String _sanitizeXML(String xml) {
771         if (Validator.isNull(xml) || (xml.indexOf("<root") == -1)) {
772             xml = _EMPTY_ROOT_NODE;
773         }
774 
775         return xml;
776     }
777 
778     private static void _setCachedValue(
779         String xml, String requestedLanguageId, boolean useDefault,
780         String value) {
781 
782         if (Validator.isNotNull(xml) && !xml.equals(_EMPTY_ROOT_NODE)) {
783             synchronized (_cache) {
784                 Map<Tuple, String> map = _cache.get(xml);
785 
786                 if (map == null) {
787                     map = new HashMap<Tuple, String>();
788                 }
789 
790                 Tuple subkey = new Tuple(useDefault, requestedLanguageId);
791 
792                 map.put(subkey, value);
793 
794                 _cache.put(xml, map);
795             }
796         }
797     }
798 
799     private static final String _AVAILABLE_LOCALES = "available-locales";
800 
801     private static final String _DEFAULT_LOCALE = "default-locale";
802 
803     private static final String _EMPTY_ROOT_NODE = "<root />";
804 
805     private static final String _LANGUAGE_ID = "language-id";
806 
807     private static final String _ROOT = "root";
808 
809     private static Log _log = LogFactoryUtil.getLog(LocalizationUtil.class);
810 
811     private static Map<String, Map<Tuple, String>> _cache = new ReferenceMap(
812         ReferenceMap.SOFT, ReferenceMap.HARD);
813 
814 }