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