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