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