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