1   /**
2    * Copyright (c) 2000-2009 Liferay, Inc. All rights reserved.
3    *
4    * Permission is hereby granted, free of charge, to any person obtaining a copy
5    * of this software and associated documentation files (the "Software"), to deal
6    * in the Software without restriction, including without limitation the rights
7    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    * copies of the Software, and to permit persons to whom the Software is
9    * furnished to do so, subject to the following conditions:
10   *
11   * The above copyright notice and this permission notice shall be included in
12   * all copies or substantial portions 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.language.LanguageUtil;
26  import com.liferay.portal.kernel.log.Log;
27  import com.liferay.portal.kernel.log.LogFactoryUtil;
28  import com.liferay.portal.kernel.util.LocaleUtil;
29  import com.liferay.portal.kernel.util.ParamUtil;
30  import com.liferay.portal.kernel.util.StringPool;
31  import com.liferay.portal.kernel.util.StringUtil;
32  import com.liferay.portal.kernel.util.Tuple;
33  import com.liferay.portal.kernel.util.Validator;
34  
35  import java.io.StringReader;
36  import java.io.StringWriter;
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   */
78  public class LocalizationUtil {
79  
80      public static String[] getAvailableLocales(String xml) {
81          String attributeValue = _getRootAttribute(
82              xml, _AVAILABLE_LOCALES, StringPool.BLANK);
83  
84          return StringUtil.split(attributeValue);
85      }
86  
87      public static String getDefaultLocale(String xml) {
88          String defaultLanguageId = LocaleUtil.toLanguageId(
89              LocaleUtil.getDefault());
90  
91          return _getRootAttribute(xml, _DEFAULT_LOCALE, defaultLanguageId);
92      }
93  
94      public static String getLocalization(
95          String xml, String requestedLanguageId) {
96  
97          return getLocalization(xml, requestedLanguageId, true);
98      }
99  
100     public static String getLocalization(
101         String xml, String requestedLanguageId, boolean useDefault) {
102 
103         String value = _getCachedValue(xml, requestedLanguageId, useDefault);
104 
105         if (value != null) {
106             return value;
107         }
108         else {
109             value = StringPool.BLANK;
110         }
111 
112         String systemDefaultLanguageId = LocaleUtil.toLanguageId(
113             LocaleUtil.getDefault());
114 
115         String defaultValue = StringPool.BLANK;
116 
117         if (!Validator.isXml(xml)) {
118             if (requestedLanguageId.equals(systemDefaultLanguageId)) {
119                 value = xml;
120             }
121             else {
122                 value = defaultValue;
123             }
124 
125             _setCachedValue(xml, requestedLanguageId, useDefault, value);
126 
127             return value;
128         }
129 
130         XMLStreamReader reader = null;
131 
132         try {
133             XMLInputFactory factory = XMLInputFactory.newInstance();
134 
135             reader = factory.createXMLStreamReader(new StringReader(xml));
136 
137             String defaultLanguageId = StringPool.BLANK;
138 
139             // Skip root node
140 
141             if (reader.hasNext()) {
142                 reader.nextTag();
143 
144                 defaultLanguageId = reader.getAttributeValue(
145                     null, _DEFAULT_LOCALE);
146 
147                 if (Validator.isNull(defaultLanguageId)) {
148                     defaultLanguageId = systemDefaultLanguageId;
149                 }
150             }
151 
152             // Find specified language and/or default language
153 
154             while (reader.hasNext()) {
155                 int event = reader.next();
156 
157                 if (event == XMLStreamConstants.START_ELEMENT) {
158                     String languageId = reader.getAttributeValue(
159                         null, _LANGUAGE_ID);
160 
161                     if (Validator.isNull(languageId)) {
162                         languageId = defaultLanguageId;
163                     }
164 
165                     if (languageId.equals(defaultLanguageId) ||
166                         languageId.equals(requestedLanguageId)) {
167 
168                         while (reader.hasNext()) {
169                             event = reader.next();
170 
171                             if (event == XMLStreamConstants.CHARACTERS ||
172                                 event == XMLStreamConstants.CDATA) {
173 
174                                 String text = reader.getText();
175 
176                                 if (languageId.equals(defaultLanguageId)) {
177                                     defaultValue = text;
178                                 }
179 
180                                 if (languageId.equals(requestedLanguageId)) {
181                                     value = text;
182                                 }
183 
184                                 break;
185                             }
186                             else if (event == XMLStreamConstants.END_ELEMENT) {
187                                 break;
188                             }
189                         }
190 
191                         if (Validator.isNotNull(value)) {
192                             break;
193                         }
194                     }
195                 }
196                 else if (event == XMLStreamConstants.END_DOCUMENT) {
197                     break;
198                 }
199             }
200 
201             if (useDefault && Validator.isNull(value)) {
202                 value = defaultValue;
203             }
204         }
205         catch (Exception e) {
206             if (_log.isWarnEnabled()) {
207                 _log.warn(e, e);
208             }
209         }
210         finally {
211             if (reader != null) {
212                 try {
213                     reader.close();
214                 }
215                 catch (Exception e) {
216                 }
217             }
218         }
219 
220         _setCachedValue(xml, requestedLanguageId, useDefault, value);
221 
222         return value;
223     }
224 
225     public static Map<Locale, String> getLocalizationMap(
226         PortletRequest portletRequest, String parameter) {
227 
228         Locale[] locales = LanguageUtil.getAvailableLocales();
229 
230         Map<Locale, String> map = new HashMap<Locale, String>();
231 
232         for (Locale locale : locales) {
233             String languageId = LocaleUtil.toLanguageId(locale);
234 
235             String localeParameter =
236                 parameter + StringPool.UNDERLINE + languageId;
237 
238             map.put(
239                 locale, ParamUtil.getString(portletRequest, localeParameter));
240         }
241 
242         return map;
243     }
244 
245     public static Map<Locale, String> getLocalizationMap(String xml) {
246         Locale[] locales = LanguageUtil.getAvailableLocales();
247 
248         Map<Locale, String> map = new HashMap<Locale, String>();
249 
250         for (Locale locale : locales) {
251             String languageId = LocaleUtil.toLanguageId(locale);
252 
253             map.put(locale, getLocalization(xml, languageId));
254         }
255 
256         return map;
257     }
258 
259     /**
260      * @deprecated Use <code>getLocalizationMap</code>.
261      */
262     public static Map<Locale, String> getLocalizedParameter(
263         PortletRequest portletRequest, String parameter) {
264 
265         return getLocalizationMap(portletRequest, parameter);
266     }
267 
268     public static String getPreferencesValue(
269         PortletPreferences preferences, String key, String languageId) {
270 
271         return getPreferencesValue(preferences, key, languageId, true);
272     }
273 
274     public static String getPreferencesValue(
275         PortletPreferences preferences, String key, String languageId,
276         boolean useDefault) {
277 
278         String localizedKey = _getPreferencesKey(key, languageId);
279 
280         String value = preferences.getValue(localizedKey, StringPool.BLANK);
281 
282         if (useDefault && Validator.isNull(value)) {
283             value = preferences.getValue(key, StringPool.BLANK);
284         }
285 
286         return value;
287     }
288 
289     public static String[] getPreferencesValues(
290         PortletPreferences preferences, String key, String languageId) {
291 
292         return getPreferencesValues(preferences, key, languageId, true);
293     }
294 
295     public static String[] getPreferencesValues(
296         PortletPreferences preferences, String key, String languageId,
297         boolean useDefault) {
298 
299         String localizedKey = _getPreferencesKey(key, languageId);
300 
301         String[] values = preferences.getValues(localizedKey, new String[0]);
302 
303         if (useDefault && Validator.isNull(values)) {
304             values = preferences.getValues(key, new String[0]);
305         }
306 
307         return values;
308     }
309 
310     public static String removeLocalization(
311         String xml, String key, String requestedLanguageId) {
312 
313         return removeLocalization(xml, key, requestedLanguageId, false);
314     }
315 
316     public static String removeLocalization(
317         String xml, String key, String requestedLanguageId, boolean cdata) {
318 
319         if (Validator.isNull(xml)) {
320             return StringPool.BLANK;
321         }
322 
323         xml = _sanitizeXML(xml);
324 
325         String systemDefaultLanguageId = LocaleUtil.toLanguageId(
326             LocaleUtil.getDefault());
327 
328         XMLStreamReader reader = null;
329         XMLStreamWriter writer = null;
330 
331         try {
332             XMLInputFactory inputFactory = XMLInputFactory.newInstance();
333 
334             reader = inputFactory.createXMLStreamReader(new StringReader(xml));
335 
336             String availableLocales = StringPool.BLANK;
337             String defaultLanguageId = StringPool.BLANK;
338 
339             // Read root node
340 
341             if (reader.hasNext()) {
342                 reader.nextTag();
343 
344                 availableLocales = reader.getAttributeValue(
345                     null, _AVAILABLE_LOCALES);
346                 defaultLanguageId = reader.getAttributeValue(
347                     null, _DEFAULT_LOCALE);
348 
349                 if (Validator.isNull(defaultLanguageId)) {
350                     defaultLanguageId = systemDefaultLanguageId;
351                 }
352             }
353 
354             if ((availableLocales != null) &&
355                 (availableLocales.indexOf(requestedLanguageId) != -1)) {
356 
357                 availableLocales = StringUtil.remove(
358                     availableLocales, requestedLanguageId, StringPool.COMMA);
359 
360                 StringWriter sw = new StringWriter(xml.length());
361 
362                 XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
363 
364                 writer = outputFactory.createXMLStreamWriter(sw);
365 
366                 writer.writeStartDocument();
367                 writer.writeStartElement(_ROOT);
368                 writer.writeAttribute(_AVAILABLE_LOCALES, availableLocales);
369                 writer.writeAttribute(_DEFAULT_LOCALE, defaultLanguageId);
370 
371                 _copyNonExempt(
372                     reader, writer, requestedLanguageId, defaultLanguageId,
373                     cdata);
374 
375                 writer.writeEndElement();
376                 writer.writeEndDocument();
377 
378                 writer.close();
379                 writer = null;
380 
381                 xml = sw.toString();
382             }
383         }
384         catch (Exception e) {
385             if (_log.isWarnEnabled()) {
386                 _log.warn(e, e);
387             }
388         }
389         finally {
390             if (reader != null) {
391                 try {
392                     reader.close();
393                 }
394                 catch (Exception e) {
395                 }
396             }
397 
398             if (writer != null) {
399                 try {
400                     writer.close();
401                 }
402                 catch (Exception e) {
403                 }
404             }
405         }
406 
407         return xml;
408     }
409 
410     public static void setLocalizedPreferencesValues (
411             ActionRequest actionRequest, PortletPreferences preferences,
412             String parameter)
413         throws Exception {
414 
415         Map<Locale, String> map = getLocalizedParameter(
416             actionRequest, parameter);
417 
418         for (Locale locale : map.keySet()) {
419             String languageId = LocaleUtil.toLanguageId(locale);
420 
421             String key = parameter + StringPool.UNDERLINE + languageId;
422             String value = map.get(locale);
423 
424             preferences.setValue(key, value);
425         }
426     }
427 
428     public static void setPreferencesValue(
429             PortletPreferences preferences, String key, String languageId,
430             String value)
431         throws Exception {
432 
433         preferences.setValue(_getPreferencesKey(key, languageId), value);
434     }
435 
436     public static void setPreferencesValues(
437             PortletPreferences preferences, String key, String languageId,
438             String[] values)
439         throws Exception {
440 
441         preferences.setValues(_getPreferencesKey(key, languageId), values);
442     }
443 
444     public static String updateLocalization(
445         String xml, String key, String value) {
446 
447         String defaultLanguageId = LocaleUtil.toLanguageId(
448             LocaleUtil.getDefault());
449 
450         return updateLocalization(
451             xml, key, value, defaultLanguageId, defaultLanguageId);
452     }
453 
454     public static String updateLocalization(
455         String xml, String key, String value, String requestedLanguageId) {
456 
457         String defaultLanguageId = LocaleUtil.toLanguageId(
458             LocaleUtil.getDefault());
459 
460         return updateLocalization(
461             xml, key, value, requestedLanguageId, defaultLanguageId);
462     }
463 
464     public static String updateLocalization(
465         String xml, String key, String value, String requestedLanguageId,
466         String defaultLanguageId) {
467 
468         return updateLocalization(
469             xml, key, value, requestedLanguageId, defaultLanguageId, false);
470     }
471 
472     public static String updateLocalization(
473         String xml, String key, String value, String requestedLanguageId,
474         String defaultLanguageId, boolean cdata) {
475 
476         xml = _sanitizeXML(xml);
477 
478         XMLStreamReader reader = null;
479         XMLStreamWriter writer = null;
480 
481         try {
482             XMLInputFactory inputFactory = XMLInputFactory.newInstance();
483 
484             reader = inputFactory.createXMLStreamReader(new StringReader(xml));
485 
486             String availableLocales = StringPool.BLANK;
487 
488             // Read root node
489 
490             if (reader.hasNext()) {
491                 reader.nextTag();
492 
493                 availableLocales = reader.getAttributeValue(
494                     null, _AVAILABLE_LOCALES);
495 
496                 if (Validator.isNull(availableLocales)) {
497                     availableLocales = defaultLanguageId;
498                 }
499 
500                 if (availableLocales.indexOf(requestedLanguageId) == -1) {
501                     availableLocales = StringUtil.add(
502                         availableLocales, requestedLanguageId,
503                         StringPool.COMMA);
504                 }
505             }
506 
507             StringWriter sw = new StringWriter(xml.length());
508 
509             XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
510 
511             writer = outputFactory.createXMLStreamWriter(sw);
512 
513             writer.writeStartDocument();
514             writer.writeStartElement(_ROOT);
515             writer.writeAttribute(_AVAILABLE_LOCALES, availableLocales);
516             writer.writeAttribute(_DEFAULT_LOCALE, defaultLanguageId);
517 
518             _copyNonExempt(
519                 reader, writer, requestedLanguageId, defaultLanguageId,
520                 cdata);
521 
522             if (cdata) {
523                 writer.writeStartElement(key);
524                 writer.writeAttribute(_LANGUAGE_ID, requestedLanguageId);
525                 writer.writeCData(value);
526                 writer.writeEndElement();
527             }
528             else {
529                 writer.writeStartElement(key);
530                 writer.writeAttribute(_LANGUAGE_ID, requestedLanguageId);
531                 writer.writeCharacters(value);
532                 writer.writeEndElement();
533             }
534 
535             writer.writeEndElement();
536             writer.writeEndDocument();
537 
538             writer.close();
539             writer = null;
540 
541             xml = sw.toString();
542         }
543         catch (Exception e) {
544             if (_log.isWarnEnabled()) {
545                 _log.warn(e, e);
546             }
547         }
548         finally {
549             if (reader != null) {
550                 try {
551                     reader.close();
552                 }
553                 catch (Exception e) {
554                 }
555             }
556 
557             if (writer != null) {
558                 try {
559                     writer.close();
560                 }
561                 catch (Exception e) {
562                 }
563             }
564         }
565 
566         return xml;
567     }
568 
569     private static void _copyNonExempt(
570             XMLStreamReader reader, XMLStreamWriter writer,
571             String exemptLanguageId, String defaultLanguageId, boolean cdata)
572         throws XMLStreamException {
573 
574         while (reader.hasNext()) {
575             int event = reader.next();
576 
577             if (event == XMLStreamConstants.START_ELEMENT) {
578                 String languageId = reader.getAttributeValue(
579                     null, _LANGUAGE_ID);
580 
581                 if (Validator.isNull(languageId)) {
582                     languageId = defaultLanguageId;
583                 }
584 
585                 if (!languageId.equals(exemptLanguageId)) {
586                     writer.writeStartElement(reader.getLocalName());
587                     writer.writeAttribute(_LANGUAGE_ID, languageId);
588 
589                     while (reader.hasNext()) {
590                         event = reader.next();
591 
592                         if (event == XMLStreamConstants.CHARACTERS ||
593                             event == XMLStreamConstants.CDATA) {
594 
595                             String text = reader.getText();
596 
597                             if (cdata) {
598                                 writer.writeCData(text);
599                             }
600                             else {
601                                 writer.writeCharacters(reader.getText());
602                             }
603 
604                             break;
605                         }
606                         else if (event == XMLStreamConstants.END_ELEMENT) {
607                             break;
608                         }
609                     }
610 
611                     writer.writeEndElement();
612                 }
613             }
614             else if (event == XMLStreamConstants.END_DOCUMENT) {
615                 break;
616             }
617         }
618     }
619 
620     private static String _getCachedValue(
621         String xml, String requestedLanguageId, boolean useDefault) {
622 
623         String value = null;
624 
625         Map<Tuple, String> valueMap = _cache.get(xml);
626 
627         if (valueMap != null) {
628             Tuple subkey = new Tuple(useDefault, requestedLanguageId);
629 
630             value = valueMap.get(subkey);
631         }
632 
633         return value;
634     }
635 
636     private static String _getPreferencesKey(String key, String languageId) {
637         String defaultLanguageId = LocaleUtil.toLanguageId(
638             LocaleUtil.getDefault());
639 
640         if (!languageId.equals(defaultLanguageId)) {
641             key += StringPool.UNDERLINE + languageId;
642         }
643 
644         return key;
645     }
646 
647     private static String _getRootAttribute(
648         String xml, String name, String defaultValue) {
649 
650         String value = null;
651 
652         XMLStreamReader reader = null;
653 
654         try {
655             XMLInputFactory factory = XMLInputFactory.newInstance();
656 
657             reader = factory.createXMLStreamReader(new StringReader(xml));
658 
659             if (reader.hasNext()) {
660                 reader.nextTag();
661 
662                 value = reader.getAttributeValue(null, name);
663             }
664         }
665         catch (Exception e) {
666             if (_log.isWarnEnabled()) {
667                 _log.warn(e, e);
668             }
669         }
670         finally {
671             if (reader != null) {
672                 try {
673                     reader.close();
674                 }
675                 catch (Exception e) {
676                 }
677             }
678         }
679 
680         if (Validator.isNull(value)) {
681             value = defaultValue;
682         }
683 
684         return value;
685     }
686 
687     private static String _sanitizeXML(String xml) {
688         if (Validator.isNull(xml) || (xml.indexOf("<root") == -1)) {
689             xml = _EMPTY_ROOT_NODE;
690         }
691 
692         return xml;
693     }
694 
695     private static void _setCachedValue(
696         String xml, String requestedLanguageId, boolean useDefault,
697         String value) {
698 
699         if (Validator.isNotNull(xml) && !xml.equals(_EMPTY_ROOT_NODE)) {
700             synchronized (_cache) {
701                 Map<Tuple, String> map = _cache.get(xml);
702 
703                 if (map == null) {
704                     map = new HashMap<Tuple, String>();
705                 }
706 
707                 Tuple subkey = new Tuple(useDefault, requestedLanguageId);
708 
709                 map.put(subkey, value);
710 
711                 _cache.put(xml, map);
712             }
713         }
714     }
715 
716     private static final String _AVAILABLE_LOCALES = "available-locales";
717 
718     private static final String _DEFAULT_LOCALE = "default-locale";
719 
720     private static final String _EMPTY_ROOT_NODE = "<root />";
721 
722     private static final String _LANGUAGE_ID = "language-id";
723 
724     private static final String _ROOT = "root";
725 
726     private static Log _log = LogFactoryUtil.getLog(LocalizationUtil.class);
727 
728     private static Map<String, Map<Tuple, String>> _cache = new ReferenceMap(
729         ReferenceMap.SOFT, ReferenceMap.HARD);
730 
731 }