1   /**
2    * Copyright (c) 2000-2008 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.log.Log;
26  import com.liferay.portal.kernel.log.LogFactoryUtil;
27  import com.liferay.portal.kernel.util.LocaleUtil;
28  import com.liferay.portal.kernel.util.StringPool;
29  import com.liferay.portal.kernel.util.StringUtil;
30  import com.liferay.portal.kernel.util.Tuple;
31  import com.liferay.portal.kernel.util.Validator;
32  
33  import java.io.StringReader;
34  import java.io.StringWriter;
35  
36  import java.util.HashMap;
37  import java.util.Map;
38  
39  import javax.portlet.PortletPreferences;
40  
41  import javax.xml.stream.XMLInputFactory;
42  import javax.xml.stream.XMLOutputFactory;
43  import javax.xml.stream.XMLStreamConstants;
44  import javax.xml.stream.XMLStreamException;
45  import javax.xml.stream.XMLStreamReader;
46  import javax.xml.stream.XMLStreamWriter;
47  
48  import org.apache.commons.collections.map.ReferenceMap;
49  
50  /**
51   * <a href="LocalizationUtil.java.html"><b><i>View Source</i></b></a>
52   *
53   * <p>
54   * This class is used to localize values stored in XML and is often used to add
55   * localization behavior to value objects.
56   * </p>
57   *
58   * <p>
59   * Caching of the localized values is done in this class rather than in the
60   * value object since value objects get flushed from cache fairly quickly.
61   * Though lookups performed on a key based on an XML file is slower than lookups
62   * done at the value object level in general, the value object will get flushed
63   * at a rate which works against the performance gain. The cache is a soft hash
64   * map which prevents memory leaks within the system while enabling the cache to
65   * live longer than in a weak hash map.
66   * </p>
67   *
68   * @author Alexander Chow
69   *
70   */
71  public class LocalizationUtil {
72  
73      public static String[] getAvailableLocales(String xml) {
74          String attributeValue = _getRootAttribute(
75              xml, _AVAILABLE_LOCALES, StringPool.BLANK);
76  
77          return StringUtil.split(attributeValue);
78      }
79  
80      public static String getDefaultLocale(String xml) {
81          String defaultLanguageId = LocaleUtil.toLanguageId(
82              LocaleUtil.getDefault());
83  
84          return _getRootAttribute(xml, _DEFAULT_LOCALE, defaultLanguageId);
85      }
86  
87      public static String getLocalization(
88          String xml, String requestedLanguageId) {
89  
90          return getLocalization(xml, requestedLanguageId, true);
91      }
92  
93      public static String getLocalization(
94          String xml, String requestedLanguageId, boolean useDefault) {
95  
96          String value = _getCachedValue(xml, requestedLanguageId, useDefault);
97  
98          if (value != null) {
99              return value;
100         }
101         else {
102             value = StringPool.BLANK;
103         }
104 
105         String defaultLanguageId = LocaleUtil.toLanguageId(
106             LocaleUtil.getDefault());
107 
108         String defaultValue = StringPool.BLANK;
109 
110         XMLStreamReader reader = null;
111 
112         try {
113             XMLInputFactory factory = XMLInputFactory.newInstance();
114 
115             reader = factory.createXMLStreamReader(new StringReader(xml));
116 
117             // Skip root node
118 
119             if (reader.hasNext()) {
120                 reader.nextTag();
121             }
122 
123             // Find specified language and/or default language
124 
125             while (reader.hasNext()) {
126                 int event = reader.next();
127 
128                 if (event == XMLStreamConstants.START_ELEMENT) {
129                     String languageId = reader.getAttributeValue(
130                         null, _LANGUAGE_ID);
131 
132                     if (Validator.isNull(languageId)) {
133                         languageId = defaultLanguageId;
134                     }
135 
136                     if (languageId.equals(defaultLanguageId) ||
137                         languageId.equals(requestedLanguageId)) {
138 
139                         while (reader.hasNext()) {
140                             event = reader.next();
141 
142                             if (event == XMLStreamConstants.CHARACTERS ||
143                                 event == XMLStreamConstants.CDATA) {
144 
145                                 String text = reader.getText();
146 
147                                 if (languageId.equals(defaultLanguageId)) {
148                                     defaultValue = text;
149                                 }
150 
151                                 if (languageId.equals(requestedLanguageId)) {
152                                     value = text;
153                                 }
154 
155                                 break;
156                             }
157                             else if (event == XMLStreamConstants.END_ELEMENT) {
158                                 break;
159                             }
160                         }
161 
162                         if (Validator.isNotNull(value)) {
163                             break;
164                         }
165                     }
166                 }
167                 else if (event == XMLStreamConstants.END_DOCUMENT){
168                     break;
169                 }
170             }
171 
172             if (useDefault && Validator.isNull(value)) {
173                 value = defaultValue;
174             }
175         }
176         catch (Exception e) {
177             if (_log.isWarnEnabled()) {
178                 _log.warn(e);
179             }
180         }
181         finally {
182             if (reader != null) {
183                 try {
184                     reader.close();
185                 }
186                 catch (Exception e) {
187                 }
188             }
189         }
190 
191         _setCachedValue(xml, requestedLanguageId, useDefault, value);
192 
193         return value;
194     }
195 
196     public static String getPrefsValue(
197         PortletPreferences prefs, String key, String languageId) {
198 
199         return getPrefsValue(prefs, key, languageId, true);
200     }
201 
202     public static String getPrefsValue(
203         PortletPreferences prefs, String key, String languageId,
204         boolean useDefault) {
205 
206         String localizedKey = _getPrefsKey(key, languageId);
207 
208         String value = prefs.getValue(localizedKey, StringPool.BLANK);
209 
210         if (useDefault && Validator.isNull(value)) {
211             value = prefs.getValue(key, StringPool.BLANK);
212         }
213 
214         return value;
215     }
216 
217     public static String[] getPrefsValues(
218         PortletPreferences prefs, String key, String languageId) {
219 
220         return getPrefsValues(prefs, key, languageId, true);
221     }
222 
223     public static String[] getPrefsValues(
224         PortletPreferences prefs, String key, String languageId,
225         boolean useDefault) {
226 
227         String localizedKey = _getPrefsKey(key, languageId);
228 
229         String[] values = prefs.getValues(localizedKey, new String[0]);
230 
231         if (useDefault && Validator.isNull(values)) {
232             values = prefs.getValues(key, new String[0]);
233         }
234 
235         return values;
236     }
237 
238     public static String removeLocalization(
239         String xml, String key, String requestedLanguageId) {
240 
241         return removeLocalization(xml, key, requestedLanguageId, false);
242     }
243 
244     public static String removeLocalization(
245         String xml, String key, String requestedLanguageId, boolean cdata) {
246 
247         xml = _sanitizeXML(xml);
248 
249         String systemDefaultLanguageId = LocaleUtil.toLanguageId(
250             LocaleUtil.getDefault());
251 
252         XMLStreamReader reader = null;
253         XMLStreamWriter writer = null;
254 
255         try {
256             XMLInputFactory inputFactory = XMLInputFactory.newInstance();
257 
258             reader = inputFactory.createXMLStreamReader(new StringReader(xml));
259 
260             String availableLocales = StringPool.BLANK;
261             String defaultLanguageId = StringPool.BLANK;
262 
263             // Read root node
264 
265             if (reader.hasNext()) {
266                 reader.nextTag();
267 
268                 availableLocales = reader.getAttributeValue(
269                     null, _AVAILABLE_LOCALES);
270                 defaultLanguageId = reader.getAttributeValue(
271                     null, _DEFAULT_LOCALE);
272 
273                 if (Validator.isNull(defaultLanguageId)) {
274                     defaultLanguageId = systemDefaultLanguageId;
275                 }
276             }
277 
278             if (availableLocales.indexOf(requestedLanguageId) != -1) {
279                 availableLocales = StringUtil.remove(
280                     availableLocales, requestedLanguageId, StringPool.COMMA);
281 
282                 StringWriter sw = new StringWriter(xml.length());
283 
284                 XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
285 
286                 writer = outputFactory.createXMLStreamWriter(sw);
287 
288                 writer.writeStartDocument();
289                 writer.writeStartElement(_ROOT);
290                 writer.writeAttribute(_AVAILABLE_LOCALES, availableLocales);
291                 writer.writeAttribute(_DEFAULT_LOCALE, defaultLanguageId);
292 
293                 _copyNonExempt(
294                     reader, writer, requestedLanguageId, defaultLanguageId,
295                     cdata);
296 
297                 writer.writeEndElement();
298                 writer.writeEndDocument();
299 
300                 writer.close();
301                 writer = null;
302 
303                 xml = sw.toString();
304             }
305         }
306         catch (Exception e) {
307             if (_log.isWarnEnabled()) {
308                 _log.warn(e);
309             }
310         }
311         finally {
312             if (reader != null) {
313                 try {
314                     reader.close();
315                 }
316                 catch (Exception e) {
317                 }
318             }
319 
320             if (writer != null) {
321                 try {
322                     writer.close();
323                 }
324                 catch (Exception e) {
325                 }
326             }
327         }
328 
329         return xml;
330     }
331 
332     public static void setPrefsValue(
333             PortletPreferences prefs, String key, String languageId,
334             String value)
335         throws Exception {
336 
337         prefs.setValue(_getPrefsKey(key, languageId), value);
338     }
339 
340     public static void setPrefsValues(
341             PortletPreferences prefs, String key, String languageId,
342             String[] values)
343         throws Exception {
344 
345         prefs.setValues(_getPrefsKey(key, languageId), values);
346     }
347 
348     public static String updateLocalization(
349         String xml, String key, String value) {
350 
351         String defaultLanguageId = LocaleUtil.toLanguageId(
352             LocaleUtil.getDefault());
353 
354         return updateLocalization(
355             xml, key, value, defaultLanguageId, defaultLanguageId);
356     }
357 
358     public static String updateLocalization(
359         String xml, String key, String value, String requestedLanguageId) {
360 
361         String defaultLanguageId = LocaleUtil.toLanguageId(
362             LocaleUtil.getDefault());
363 
364         return updateLocalization(
365             xml, key, value, requestedLanguageId, defaultLanguageId);
366     }
367 
368     public static String updateLocalization(
369         String xml, String key, String value, String requestedLanguageId,
370         String defaultLanguageId) {
371 
372         return updateLocalization(
373             xml, key, value, requestedLanguageId, defaultLanguageId, false);
374     }
375 
376     public static String updateLocalization(
377         String xml, String key, String value, String requestedLanguageId,
378         String defaultLanguageId, boolean cdata) {
379 
380         xml = _sanitizeXML(xml);
381 
382         XMLStreamReader reader = null;
383         XMLStreamWriter writer = null;
384 
385         try {
386             XMLInputFactory inputFactory = XMLInputFactory.newInstance();
387 
388             reader = inputFactory.createXMLStreamReader(new StringReader(xml));
389 
390             String availableLocales = StringPool.BLANK;
391 
392             // Read root node
393 
394             if (reader.hasNext()) {
395                 reader.nextTag();
396 
397                 availableLocales = reader.getAttributeValue(
398                     null, _AVAILABLE_LOCALES);
399 
400                 if (Validator.isNull(availableLocales)) {
401                     availableLocales = defaultLanguageId;
402                 }
403 
404                 if (availableLocales.indexOf(requestedLanguageId) == -1) {
405                     availableLocales = StringUtil.add(
406                         availableLocales, requestedLanguageId,
407                         StringPool.COMMA);
408                 }
409             }
410 
411             StringWriter sw = new StringWriter(xml.length());
412 
413             XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
414 
415             writer = outputFactory.createXMLStreamWriter(sw);
416 
417             writer.writeStartDocument();
418             writer.writeStartElement(_ROOT);
419             writer.writeAttribute(_AVAILABLE_LOCALES, availableLocales);
420             writer.writeAttribute(_DEFAULT_LOCALE, defaultLanguageId);
421 
422             _copyNonExempt(
423                 reader, writer, requestedLanguageId, defaultLanguageId,
424                 cdata);
425 
426             if (cdata) {
427                 writer.writeStartElement(key);
428                 writer.writeAttribute(_LANGUAGE_ID, requestedLanguageId);
429                 writer.writeCData(value);
430                 writer.writeEndElement();
431             }
432             else {
433                 writer.writeStartElement(key);
434                 writer.writeAttribute(_LANGUAGE_ID, requestedLanguageId);
435                 writer.writeCharacters(value);
436                 writer.writeEndElement();
437             }
438 
439             writer.writeEndElement();
440             writer.writeEndDocument();
441 
442             writer.close();
443             writer = null;
444 
445             xml = sw.toString();
446         }
447         catch (Exception e) {
448             if (_log.isWarnEnabled()) {
449                 _log.warn(e);
450             }
451         }
452         finally {
453             if (reader != null) {
454                 try {
455                     reader.close();
456                 }
457                 catch (Exception e) {
458                 }
459             }
460 
461             if (writer != null) {
462                 try {
463                     writer.close();
464                 }
465                 catch (Exception e) {
466                 }
467             }
468         }
469 
470         return xml;
471     }
472 
473     private static void _copyNonExempt(
474             XMLStreamReader reader, XMLStreamWriter writer,
475             String exemptLanguageId, String defaultLanguageId, boolean cdata)
476         throws XMLStreamException {
477 
478         while (reader.hasNext()) {
479             int event = reader.next();
480 
481             if (event == XMLStreamConstants.START_ELEMENT) {
482                 String languageId = reader.getAttributeValue(
483                     null, _LANGUAGE_ID);
484 
485                 if (Validator.isNull(languageId)) {
486                     languageId = defaultLanguageId;
487                 }
488 
489                 if (!languageId.equals(exemptLanguageId)) {
490                     writer.writeStartElement(reader.getLocalName());
491                     writer.writeAttribute(_LANGUAGE_ID, languageId);
492 
493                     while (reader.hasNext()) {
494                         event = reader.next();
495 
496                         if (event == XMLStreamConstants.CHARACTERS ||
497                             event == XMLStreamConstants.CDATA) {
498 
499                             String text = reader.getText();
500 
501                             if (cdata) {
502                                 writer.writeCData(text);
503                             }
504                             else {
505                                 writer.writeCharacters(reader.getText());
506                             }
507 
508                             break;
509                         }
510                         else if (event == XMLStreamConstants.END_ELEMENT) {
511                             break;
512                         }
513                     }
514 
515                     writer.writeEndElement();
516                 }
517             }
518             else if (event == XMLStreamConstants.END_DOCUMENT) {
519                 break;
520             }
521         }
522     }
523 
524     private static String _getCachedValue(
525         String xml, String requestedLanguageId, boolean useDefault) {
526 
527         String value = null;
528 
529         Map<Tuple, String> valueMap = _cache.get(xml);
530 
531         if (valueMap != null) {
532             Tuple subkey = new Tuple(useDefault, requestedLanguageId);
533 
534             value = valueMap.get(subkey);
535         }
536 
537         return value;
538     }
539 
540     private static String _getPrefsKey(String key, String languageId) {
541         String defaultLanguageId = LocaleUtil.toLanguageId(
542             LocaleUtil.getDefault());
543 
544         if (!languageId.equals(defaultLanguageId)) {
545             key += StringPool.UNDERLINE + languageId;
546         }
547 
548         return key;
549     }
550 
551     private static String _getRootAttribute(
552         String xml, String name, String defaultValue) {
553 
554         String value = null;
555 
556         XMLStreamReader reader = null;
557 
558         try {
559             XMLInputFactory factory = XMLInputFactory.newInstance();
560 
561             reader = factory.createXMLStreamReader(new StringReader(xml));
562 
563             if (reader.hasNext()) {
564                 reader.nextTag();
565 
566                 value = reader.getAttributeValue(null, name);
567             }
568         }
569         catch (Exception e) {
570             if (_log.isWarnEnabled()) {
571                 _log.warn(e);
572             }
573         }
574         finally {
575             if (reader != null) {
576                 try {
577                     reader.close();
578                 }
579                 catch (Exception e) {
580                 }
581             }
582         }
583 
584         if (Validator.isNull(value)) {
585             value = defaultValue;
586         }
587 
588         return value;
589     }
590 
591     private static String _sanitizeXML(String xml) {
592         if (Validator.isNull(xml) || (xml.indexOf("<root") == -1)) {
593             xml = _EMPTY_ROOT_NODE;
594         }
595 
596         return xml;
597     }
598 
599     private static void _setCachedValue(
600         String xml, String requestedLanguageId, boolean useDefault,
601         String value) {
602 
603         if (Validator.isNotNull(xml) && !xml.equals(_EMPTY_ROOT_NODE)) {
604             synchronized (_cache) {
605                 Map<Tuple, String> valueMap = _cache.get(xml);
606 
607                 if (valueMap == null) {
608                     valueMap = new HashMap<Tuple, String>();
609                 }
610 
611                 Tuple subkey = new Tuple(useDefault, requestedLanguageId);
612 
613                 valueMap.put(subkey, value);
614 
615                 _cache.put(xml, valueMap);
616             }
617         }
618     }
619 
620     private static final String _AVAILABLE_LOCALES = "available-locales";
621 
622     private static final String _DEFAULT_LOCALE = "default-locale";
623 
624     private static final String _LANGUAGE_ID = "language-id";
625 
626     private static final String _ROOT = "root";
627 
628     private static final String _EMPTY_ROOT_NODE = "<root />";
629 
630     private static Log _log = LogFactoryUtil.getLog(LocalizationUtil.class);
631 
632     private static Map<String, Map<Tuple, String>> _cache = new ReferenceMap(
633         ReferenceMap.SOFT, ReferenceMap.HARD);
634 
635 }