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