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