001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.language;
016    
017    import com.liferay.portal.kernel.language.LanguageUtil;
018    import com.liferay.portal.kernel.log.Log;
019    import com.liferay.portal.kernel.log.LogFactoryUtil;
020    import com.liferay.portal.kernel.util.CharPool;
021    import com.liferay.portal.kernel.util.GetterUtil;
022    import com.liferay.portal.kernel.util.LocaleUtil;
023    import com.liferay.portal.kernel.util.PropertiesUtil;
024    import com.liferay.portal.kernel.util.StringBundler;
025    import com.liferay.portal.kernel.util.StringPool;
026    import com.liferay.portal.kernel.util.StringUtil;
027    import com.liferay.portal.kernel.util.Validator;
028    import com.liferay.portal.tools.LangBuilder;
029    import com.liferay.registry.Filter;
030    import com.liferay.registry.Registry;
031    import com.liferay.registry.RegistryUtil;
032    import com.liferay.registry.ServiceReference;
033    import com.liferay.registry.ServiceTracker;
034    import com.liferay.registry.ServiceTrackerCustomizer;
035    
036    import java.io.InputStream;
037    
038    import java.net.URL;
039    
040    import java.util.Collections;
041    import java.util.Enumeration;
042    import java.util.HashMap;
043    import java.util.Locale;
044    import java.util.Map;
045    import java.util.Properties;
046    import java.util.ResourceBundle;
047    import java.util.Set;
048    import java.util.concurrent.ConcurrentHashMap;
049    
050    /**
051     * @author Shuyang Zhou
052     * @author Kamesh Sampath
053     */
054    public class LanguageResources {
055    
056            public static String fixValue(String value) {
057                    if (value.endsWith(LangBuilder.AUTOMATIC_COPY)) {
058                            value = value.substring(
059                                    0, value.length() - LangBuilder.AUTOMATIC_COPY.length());
060                    }
061    
062                    if (value.endsWith(LangBuilder.AUTOMATIC_TRANSLATION)) {
063                            value = value.substring(
064                                    0, value.length() - LangBuilder.AUTOMATIC_TRANSLATION.length());
065                    }
066    
067                    return value;
068            }
069    
070            public static void fixValues(
071                    Map<String, String> languageMap, Properties properties) {
072    
073                    for (Map.Entry<Object, Object> entry : properties.entrySet()) {
074                            String key = (String)entry.getKey();
075                            String value = (String)entry.getValue();
076    
077                            value = fixValue(value);
078    
079                            languageMap.put(key, value);
080                    }
081            }
082    
083            public static String getMessage(Locale locale, String key) {
084                    if (locale == null) {
085                            return null;
086                    }
087    
088                    Map<String, String> languageMap = _languageMaps.get(locale);
089    
090                    if (languageMap == null) {
091                            languageMap = _loadLocale(locale);
092                    }
093    
094                    String value = languageMap.get(key);
095    
096                    if (value == null) {
097                            return getMessage(getSuperLocale(locale), key);
098                    }
099                    else {
100                            return value;
101                    }
102            }
103    
104            public static ResourceBundle getResourceBundle(Locale locale) {
105                    return new LanguageResourcesBundle(locale);
106            }
107    
108            public static Locale getSuperLocale(Locale locale) {
109                    Locale superLocale = _superLocales.get(locale);
110    
111                    if (superLocale != null) {
112                            if (superLocale == _nullLocale) {
113                                    return null;
114                            }
115    
116                            return superLocale;
117                    }
118    
119                    superLocale = _getSuperLocale(locale);
120    
121                    if (superLocale == null) {
122                            _superLocales.put(locale, _nullLocale);
123                    }
124                    else {
125                            _superLocales.put(locale, superLocale);
126                    }
127    
128                    return superLocale;
129            }
130    
131            public void setConfig(String config) {
132                    _configNames = StringUtil.split(
133                            config.replace(CharPool.PERIOD, CharPool.SLASH));
134            }
135    
136            private static Locale _getSuperLocale(Locale locale) {
137                    String variant = locale.getVariant();
138    
139                    if (variant.length() > 0) {
140                            return new Locale(locale.getLanguage(), locale.getCountry());
141                    }
142    
143                    String country = locale.getCountry();
144    
145                    if (country.length() > 0) {
146                            Locale priorityLocale = LanguageUtil.getLocale(
147                                    locale.getLanguage());
148    
149                            if ((priorityLocale != null) && !locale.equals(priorityLocale)) {
150                                    return new Locale(
151                                            priorityLocale.getLanguage(), priorityLocale.getCountry());
152                            }
153    
154                            return LocaleUtil.fromLanguageId(locale.getLanguage(), false, true);
155                    }
156    
157                    String language = locale.getLanguage();
158    
159                    if (language.length() > 0) {
160                            return _blankLocale;
161                    }
162    
163                    return null;
164            }
165    
166            private static Map<String, String> _loadLocale(Locale locale) {
167                    Map<String, String> languageMap = null;
168    
169                    if (_configNames.length > 0) {
170                            String localeName = locale.toString();
171    
172                            languageMap = new HashMap<String, String>();
173    
174                            for (String name : _configNames) {
175                                    StringBundler sb = new StringBundler(4);
176    
177                                    sb.append(name);
178    
179                                    if (localeName.length() > 0) {
180                                            sb.append(StringPool.UNDERLINE);
181                                            sb.append(localeName);
182                                    }
183    
184                                    sb.append(".properties");
185    
186                                    Properties properties = _loadProperties(sb.toString());
187    
188                                    fixValues(languageMap, properties);
189                            }
190                    }
191                    else {
192                            languageMap = Collections.emptyMap();
193                    }
194    
195                    _languageMaps.put(locale, languageMap);
196    
197                    return languageMap;
198            }
199    
200            private static Properties _loadProperties(String name) {
201                    Properties properties = new Properties();
202    
203                    try {
204                            ClassLoader classLoader = LanguageResources.class.getClassLoader();
205    
206                            Enumeration<URL> enu = classLoader.getResources(name);
207    
208                            if (_log.isDebugEnabled() && !enu.hasMoreElements()) {
209                                    _log.debug("No resources found for " + name);
210                            }
211    
212                            while (enu.hasMoreElements()) {
213                                    URL url = enu.nextElement();
214    
215                                    if (_log.isInfoEnabled()) {
216                                            _log.info("Loading " + name + " from " + url);
217                                    }
218    
219                                    try (InputStream inputStream = url.openStream()) {
220                                            Properties inputStreamProperties = PropertiesUtil.load(
221                                                    inputStream, StringPool.UTF8);
222    
223                                            properties.putAll(inputStreamProperties);
224    
225                                            if (_log.isInfoEnabled()) {
226                                                    _log.info(
227                                                            "Loading " + url + " with " +
228                                                                    inputStreamProperties.size() + " values");
229                                            }
230                                    }
231                            }
232                    }
233                    catch (Exception e) {
234                            if (_log.isWarnEnabled()) {
235                                    _log.warn(e, e);
236                            }
237                    }
238    
239                    return properties;
240            }
241    
242            private static Map<String, String> _putLanguageMap(
243                    Locale locale, Map<String, String> languageMap) {
244    
245                    Map<String, String> oldLanguageMap = _languageMaps.get(locale);
246    
247                    if (oldLanguageMap == null) {
248                            _loadLocale(locale);
249    
250                            oldLanguageMap = _languageMaps.get(locale);
251                    }
252    
253                    Map<String, String> newLanguageMap = new HashMap<String, String>();
254    
255                    if (oldLanguageMap != null) {
256                            newLanguageMap.putAll(oldLanguageMap);
257                    }
258    
259                    newLanguageMap.putAll(languageMap);
260    
261                    _languageMaps.put(locale, newLanguageMap);
262    
263                    return oldLanguageMap;
264            }
265    
266            private static final Log _log = LogFactoryUtil.getLog(
267                    LanguageResources.class);
268    
269            private static final Locale _blankLocale = new Locale(StringPool.BLANK);
270            private static String[] _configNames;
271            private static final Map<Locale, Map<String, String>> _languageMaps =
272                    new ConcurrentHashMap<Locale, Map<String, String>>(64);
273            private static final Locale _nullLocale = new Locale(StringPool.BLANK);
274            private static final ServiceTracker<ResourceBundle, ResourceBundle>
275                    _serviceTracker;
276            private static final Map<Locale, Locale> _superLocales =
277                    new ConcurrentHashMap<Locale, Locale>();
278    
279            static {
280                    Registry registry = RegistryUtil.getRegistry();
281    
282                    Filter languageResourceFilter = registry.getFilter(
283                            "(&(!(javax.portlet.name=*))(language.id=*)(objectClass=" +
284                                    ResourceBundle.class.getName() + "))");
285    
286                    _serviceTracker = registry.trackServices(
287                            languageResourceFilter,
288                            new LanguageResourceServiceTrackerCustomizer());
289    
290                    _serviceTracker.open();
291            }
292    
293            private static class LanguageResourcesBundle extends ResourceBundle {
294    
295                    @Override
296                    public Enumeration<String> getKeys() {
297                            Set<String> keySet = _languageMap.keySet();
298    
299                            if (parent == null) {
300                                    return Collections.enumeration(keySet);
301                            }
302    
303                            return new ResourceBundleEnumeration(keySet, parent.getKeys());
304                    }
305    
306                    @Override
307                    public Locale getLocale() {
308                            return _locale;
309                    }
310    
311                    @Override
312                    protected Object handleGetObject(String key) {
313                            return _languageMap.get(key);
314                    }
315    
316                    @Override
317                    protected Set<String> handleKeySet() {
318                            return _languageMap.keySet();
319                    }
320    
321                    private LanguageResourcesBundle(Locale locale) {
322                            _locale = locale;
323    
324                            Map<String, String> languageMap = _languageMaps.get(locale);
325    
326                            if (languageMap == null) {
327                                    languageMap = _loadLocale(locale);
328                            }
329    
330                            _languageMap = languageMap;
331    
332                            Locale superLocale = getSuperLocale(locale);
333    
334                            if (superLocale != null) {
335                                    setParent(new LanguageResourcesBundle(superLocale));
336                            }
337                    }
338    
339                    private final Map<String, String> _languageMap;
340                    private final Locale _locale;
341    
342            }
343    
344            private static class LanguageResourceServiceTrackerCustomizer
345                    implements ServiceTrackerCustomizer<ResourceBundle, ResourceBundle> {
346    
347                    @Override
348                    public ResourceBundle addingService(
349                            ServiceReference<ResourceBundle> serviceReference) {
350    
351                            Registry registry = RegistryUtil.getRegistry();
352    
353                            ResourceBundle resourceBundle = registry.getService(
354                                    serviceReference);
355    
356                            String languageId = GetterUtil.getString(
357                                    serviceReference.getProperty("language.id"), StringPool.BLANK);
358                            Map<String, String> languageMap = new HashMap<String, String>();
359                            Locale locale = null;
360    
361                            if (Validator.isNotNull(languageId)) {
362                                    locale = LocaleUtil.fromLanguageId(languageId, true);
363                            }
364                            else {
365                                    locale = new Locale(StringPool.BLANK);
366                            }
367    
368                            Enumeration<String> keys = resourceBundle.getKeys();
369    
370                            while (keys.hasMoreElements()) {
371                                    String key = keys.nextElement();
372    
373                                    String value = resourceBundle.getString(key);
374    
375                                    languageMap.put(key, value);
376                            }
377    
378                            Map<String, String> oldLanguageMap = _putLanguageMap(
379                                    locale, languageMap);
380    
381                            _oldLanguageMaps.put(serviceReference, oldLanguageMap);
382    
383                            return resourceBundle;
384                    }
385    
386                    @Override
387                    public void modifiedService(
388                            ServiceReference<ResourceBundle> serviceReference,
389                            ResourceBundle resourceBundle) {
390                    }
391    
392                    @Override
393                    public void removedService(
394                            ServiceReference<ResourceBundle> serviceReference,
395                            ResourceBundle resourceBundle) {
396    
397                            Registry registry = RegistryUtil.getRegistry();
398    
399                            registry.ungetService(serviceReference);
400    
401                            String languageId = GetterUtil.getString(
402                                    serviceReference.getProperty("language.id"), StringPool.BLANK);
403                            Locale locale = null;
404    
405                            if (Validator.isNotNull(languageId)) {
406                                    locale = LocaleUtil.fromLanguageId(languageId, true);
407                            }
408                            else {
409                                    locale = new Locale(StringPool.BLANK);
410                            }
411    
412                            Map<String, String> languageMap = _oldLanguageMaps.get(
413                                    serviceReference);
414    
415                            _putLanguageMap(locale, languageMap);
416                    }
417    
418                    private final Map<ServiceReference<?>, Map<String, String>>
419                            _oldLanguageMaps = new HashMap<>();
420    
421            }
422    
423    }