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