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.cache.MultiVMPool;
018    import com.liferay.portal.kernel.cache.PortalCache;
019    import com.liferay.portal.kernel.cache.PortalCacheMapSynchronizeUtil;
020    import com.liferay.portal.kernel.cache.PortalCacheMapSynchronizeUtil.Synchronizer;
021    import com.liferay.portal.kernel.exception.PortalException;
022    import com.liferay.portal.kernel.exception.SystemException;
023    import com.liferay.portal.kernel.language.Language;
024    import com.liferay.portal.kernel.language.LanguageWrapper;
025    import com.liferay.portal.kernel.log.Log;
026    import com.liferay.portal.kernel.log.LogFactoryUtil;
027    import com.liferay.portal.kernel.model.CompanyConstants;
028    import com.liferay.portal.kernel.model.Group;
029    import com.liferay.portal.kernel.model.GroupConstants;
030    import com.liferay.portal.kernel.security.auth.CompanyThreadLocal;
031    import com.liferay.portal.kernel.security.pacl.DoPrivileged;
032    import com.liferay.portal.kernel.service.GroupLocalServiceUtil;
033    import com.liferay.portal.kernel.theme.ThemeDisplay;
034    import com.liferay.portal.kernel.util.ArrayUtil;
035    import com.liferay.portal.kernel.util.CharPool;
036    import com.liferay.portal.kernel.util.CookieKeys;
037    import com.liferay.portal.kernel.util.GetterUtil;
038    import com.liferay.portal.kernel.util.HtmlUtil;
039    import com.liferay.portal.kernel.util.JavaConstants;
040    import com.liferay.portal.kernel.util.LocaleUtil;
041    import com.liferay.portal.kernel.util.ObjectValuePair;
042    import com.liferay.portal.kernel.util.ParamUtil;
043    import com.liferay.portal.kernel.util.PortalUtil;
044    import com.liferay.portal.kernel.util.PropsKeys;
045    import com.liferay.portal.kernel.util.ResourceBundleLoader;
046    import com.liferay.portal.kernel.util.ResourceBundleUtil;
047    import com.liferay.portal.kernel.util.StringBundler;
048    import com.liferay.portal.kernel.util.StringPool;
049    import com.liferay.portal.kernel.util.StringUtil;
050    import com.liferay.portal.kernel.util.Time;
051    import com.liferay.portal.kernel.util.UnicodeProperties;
052    import com.liferay.portal.kernel.util.Validator;
053    import com.liferay.portal.kernel.util.WebKeys;
054    import com.liferay.portal.util.PrefsPropsUtil;
055    import com.liferay.portal.util.PropsValues;
056    import com.liferay.registry.Registry;
057    import com.liferay.registry.RegistryUtil;
058    import com.liferay.registry.dependency.ServiceDependencyListener;
059    import com.liferay.registry.dependency.ServiceDependencyManager;
060    
061    import java.io.Serializable;
062    
063    import java.text.MessageFormat;
064    import java.text.NumberFormat;
065    
066    import java.util.Collections;
067    import java.util.HashMap;
068    import java.util.HashSet;
069    import java.util.List;
070    import java.util.Locale;
071    import java.util.Map;
072    import java.util.ResourceBundle;
073    import java.util.Set;
074    import java.util.concurrent.ConcurrentHashMap;
075    import java.util.regex.Matcher;
076    import java.util.regex.Pattern;
077    
078    import javax.portlet.PortletConfig;
079    import javax.portlet.PortletRequest;
080    
081    import javax.servlet.http.Cookie;
082    import javax.servlet.http.HttpServletRequest;
083    import javax.servlet.http.HttpServletResponse;
084    
085    /**
086     * Provides various translation related functionalities for language keys
087     * specified in portlet configurations and portal resource bundles.
088     *
089     * <p>
090     * You can disable translations by setting the
091     * <code>translations.disabled</code> property to <code>true</code> in
092     * <code>portal.properties</code>.
093     * </p>
094     *
095     * <p>
096     * Depending on the context passed into these methods, the lookup might be
097     * limited to the portal's resource bundle (e.g. when only a locale is passed),
098     * or extended to include an individual portlet's resource bundle (e.g. when a
099     * request object is passed). A portlet's resource bundle overrides the portal's
100     * resources when both are present.
101     * </p>
102     *
103     * @author Brian Wing Shun Chan
104     * @author Andrius Vitkauskas
105     * @author Eduardo Lundgren
106     */
107    @DoPrivileged
108    public class LanguageImpl implements Language, Serializable {
109    
110            public void afterPropertiesSet() {
111                    ServiceDependencyManager serviceDependencyManager =
112                            new ServiceDependencyManager();
113    
114                    serviceDependencyManager.addServiceDependencyListener(
115                            new ServiceDependencyListener() {
116    
117                                    @Override
118                                    public void dependenciesFulfilled() {
119                                            Registry registry = RegistryUtil.getRegistry();
120    
121                                            MultiVMPool multiVMPool = registry.getService(
122                                                    MultiVMPool.class);
123    
124                                            _companyLocalesPortalCache =
125                                                    (PortalCache<Long, Serializable>)
126                                                            multiVMPool.getPortalCache(
127                                                                    _COMPANY_LOCALES_PORTAL_CACHE_NAME);
128    
129                                            PortalCacheMapSynchronizeUtil.synchronize(
130                                                    _companyLocalesPortalCache, _companyLocalesBags,
131                                                    _removeSynchronizer);
132    
133                                            _groupLocalesPortalCache =
134                                                    (PortalCache<Long, Serializable>)
135                                                            multiVMPool.getPortalCache(
136                                                                    _GROUP_LOCALES_PORTAL_CACHE_NAME);
137    
138                                            PortalCacheMapSynchronizeUtil.synchronize(
139                                                    _groupLocalesPortalCache,
140                                                    _groupLanguageCodeLocalesMapMap, _removeSynchronizer);
141    
142                                            PortalCacheMapSynchronizeUtil.synchronize(
143                                                    _groupLocalesPortalCache, _groupLanguageIdLocalesMap,
144                                                    _removeSynchronizer);
145                                    }
146    
147                                    @Override
148                                    public void destroy() {
149                                    }
150    
151                            });
152    
153                    serviceDependencyManager.registerDependencies(MultiVMPool.class);
154            }
155    
156            /**
157             * Returns the translated pattern using the current request's locale or, if
158             * the current request locale is not available, the server's default locale.
159             *
160             * <p>
161             * The lookup is done on the portlet configuration first, and if it's not
162             * found, it is done on the portal's resource bundle. If a translation for a
163             * given key does not exist, this method returns the requested key as the
164             * translation.
165             * </p>
166             *
167             * <p>
168             * The substitute placeholder (e.g. <code>{0}</code>) is replaced with the
169             * argument, following the standard Java {@link ResourceBundle} notion of
170             * index based substitution.
171             * </p>
172             *
173             * @param  request the request used to determine the current locale
174             * @param  pattern the key to look up in the current locale's resource file.
175             *         The key follows the standard Java resource specification.
176             * @param  argument the single argument to be substituted into the pattern
177             *         and translated, if possible
178             * @return the translated pattern, with the argument substituted in for the
179             *         pattern's placeholder
180             */
181            @Override
182            public String format(
183                    HttpServletRequest request, String pattern, LanguageWrapper argument) {
184    
185                    return format(request, pattern, new LanguageWrapper[] {argument}, true);
186            }
187    
188            /**
189             * Returns the translated pattern using the current request's locale or, if
190             * the current request locale is not available, the server's default locale.
191             *
192             * <p>
193             * The lookup is done on the portlet configuration first, and if it's not
194             * found, it is done on the portal's resource bundle. If a translation for a
195             * given key does not exist, this method returns the requested key as the
196             * translation.
197             * </p>
198             *
199             * <p>
200             * The substitute placeholder (e.g. <code>{0}</code>) is replaced with the
201             * argument, following the standard Java {@link ResourceBundle} notion of
202             * index based substitution.
203             * </p>
204             *
205             * @param  request the request used to determine the current locale
206             * @param  pattern the key to look up in the current locale's resource file.
207             *         The key follows the standard Java resource specification.
208             * @param  argument the single argument to be substituted into the pattern
209             *         and translated, if possible
210             * @param  translateArguments whether the argument is translated
211             * @return the translated pattern, with the argument substituted in for the
212             *         pattern's placeholder
213             */
214            @Override
215            public String format(
216                    HttpServletRequest request, String pattern, LanguageWrapper argument,
217                    boolean translateArguments) {
218    
219                    return format(
220                            request, pattern, new LanguageWrapper[] {argument},
221                            translateArguments);
222            }
223    
224            /**
225             * Returns the translated pattern using the current request's locale or, if
226             * the current request locale is not available, the server's default locale.
227             *
228             * <p>
229             * The lookup is done on the portlet configuration first, and if it's not
230             * found, it is done on the portal's resource bundle. If a translation for a
231             * given key does not exist, this method returns the requested key as the
232             * translation.
233             * </p>
234             *
235             * <p>
236             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
237             * <code>{2}</code>, etc.) are replaced with the arguments, following the
238             * standard Java {@link ResourceBundle} notion of index based substitution.
239             * </p>
240             *
241             * @param  request the request used to determine the current locale
242             * @param  pattern the key to look up in the current locale's resource file.
243             *         The key follows the standard Java resource specification.
244             * @param  arguments the arguments to be substituted into the pattern and
245             *         translated, if possible
246             * @return the translated pattern, with the arguments substituted in for the
247             *         pattern's placeholders
248             */
249            @Override
250            public String format(
251                    HttpServletRequest request, String pattern,
252                    LanguageWrapper[] arguments) {
253    
254                    return format(request, pattern, arguments, true);
255            }
256    
257            /**
258             * Returns the translated pattern using the current request's locale or, if
259             * the current request locale is not available, the server's default locale.
260             *
261             * <p>
262             * The lookup is done on the portlet configuration first, and if it's not
263             * found, it is done on the portal's resource bundle. If a translation for a
264             * given key does not exist, this method returns the requested key as the
265             * translation.
266             * </p>
267             *
268             * <p>
269             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
270             * <code>{2}</code>, etc.) are replaced with the arguments, following the
271             * standard Java {@link ResourceBundle} notion of index based substitution.
272             * </p>
273             *
274             * @param  request the request used to determine the current locale
275             * @param  pattern the key to look up in the current locale's resource file.
276             *         The key follows the standard Java resource specification.
277             * @param  arguments the arguments to be substituted into the pattern
278             * @param  translateArguments whether the arguments are translated
279             * @return the translated pattern, with the arguments substituted in for the
280             *         pattern's placeholders
281             */
282            @Override
283            public String format(
284                    HttpServletRequest request, String pattern, LanguageWrapper[] arguments,
285                    boolean translateArguments) {
286    
287                    if (PropsValues.TRANSLATIONS_DISABLED) {
288                            return pattern;
289                    }
290    
291                    String value = null;
292    
293                    try {
294                            pattern = get(request, pattern);
295    
296                            if (ArrayUtil.isNotEmpty(arguments)) {
297                                    pattern = _escapePattern(pattern);
298    
299                                    Object[] formattedArguments = new Object[arguments.length];
300    
301                                    for (int i = 0; i < arguments.length; i++) {
302                                            if (translateArguments) {
303                                                    formattedArguments[i] =
304                                                            arguments[i].getBefore() +
305                                                                    get(request, arguments[i].getText()) +
306                                                                            arguments[i].getAfter();
307                                            }
308                                            else {
309                                                    formattedArguments[i] =
310                                                            arguments[i].getBefore() + arguments[i].getText() +
311                                                                    arguments[i].getAfter();
312                                            }
313                                    }
314    
315                                    MessageFormat messageFormat = decorateMessageFormat(
316                                            request, pattern, formattedArguments);
317    
318                                    value = messageFormat.format(formattedArguments);
319                            }
320                            else {
321                                    value = pattern;
322                            }
323                    }
324                    catch (Exception e) {
325                            if (_log.isWarnEnabled()) {
326                                    _log.warn(e, e);
327                            }
328                    }
329    
330                    return value;
331            }
332    
333            /**
334             * Returns the translated pattern using the current request's locale or, if
335             * the current request locale is not available, the server's default locale.
336             *
337             * <p>
338             * The lookup is done on the portlet configuration first, and if it's not
339             * found, it is done on the portal's resource bundle. If a translation for a
340             * given key does not exist, this method returns the requested key as the
341             * translation.
342             * </p>
343             *
344             * <p>
345             * The substitute placeholder (e.g. <code>{0}</code>) is replaced with the
346             * argument, following the standard Java {@link ResourceBundle} notion of
347             * index based substitution.
348             * </p>
349             *
350             * @param  request the request used to determine the current locale
351             * @param  pattern the key to look up in the current locale's resource file.
352             *         The key follows the standard Java resource specification.
353             * @param  argument the single argument to be substituted into the pattern
354             *         and translated, if possible
355             * @return the translated pattern, with the argument substituted in for the
356             *         pattern's placeholder
357             */
358            @Override
359            public String format(
360                    HttpServletRequest request, String pattern, Object argument) {
361    
362                    return format(request, pattern, new Object[] {argument}, true);
363            }
364    
365            /**
366             * Returns the translated pattern using the current request's locale or, if
367             * the current request locale is not available, the server's default locale.
368             *
369             * <p>
370             * The lookup is done on the portlet configuration first, and if it's not
371             * found, it is done on the portal's resource bundle. If a translation for a
372             * given key does not exist, this method returns the requested key as the
373             * translation.
374             * </p>
375             *
376             * <p>
377             * The substitute placeholder (e.g. <code>{0}</code>) is replaced with the
378             * argument, following the standard Java {@link ResourceBundle} notion of
379             * index based substitution.
380             * </p>
381             *
382             * @param  request the request used to determine the current locale
383             * @param  pattern the key to look up in the current locale's resource file.
384             *         The key follows the standard Java resource specification.
385             * @param  argument the single argument to be substituted into the pattern
386             *         and translated, if possible
387             * @param  translateArguments whether the argument is translated
388             * @return the translated pattern, with the argument substituted in for the
389             *         pattern's placeholder
390             */
391            @Override
392            public String format(
393                    HttpServletRequest request, String pattern, Object argument,
394                    boolean translateArguments) {
395    
396                    return format(
397                            request, pattern, new Object[] {argument}, translateArguments);
398            }
399    
400            /**
401             * Returns the translated pattern using the current request's locale or, if
402             * the current request locale is not available, the server's default locale.
403             *
404             * <p>
405             * The lookup is done on the portlet configuration first, and if it's not
406             * found, it is done on the portal's resource bundle. If a translation for a
407             * given key does not exist, this method returns the requested key as the
408             * translation.
409             * </p>
410             *
411             * <p>
412             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
413             * <code>{2}</code>, etc.) are replaced with the arguments, following the
414             * standard Java {@link ResourceBundle} notion of index based substitution.
415             * </p>
416             *
417             * @param  request the request used to determine the current locale
418             * @param  pattern the key to look up in the current locale's resource file.
419             *         The key follows the standard Java resource specification.
420             * @param  arguments the arguments to be substituted into the pattern and
421             *         translated, if possible
422             * @return the translated pattern, with the arguments substituted in for the
423             *         pattern's placeholders
424             */
425            @Override
426            public String format(
427                    HttpServletRequest request, String pattern, Object[] arguments) {
428    
429                    return format(request, pattern, arguments, true);
430            }
431    
432            /**
433             * Returns the translated pattern using the current request's locale or, if
434             * the current request locale is not available, the server's default locale.
435             *
436             * <p>
437             * The lookup is done on the portlet configuration first, and if it's not
438             * found, it is done on the portal's resource bundle. If a translation for a
439             * given key does not exist, this method returns the requested key as the
440             * translation.
441             * </p>
442             *
443             * <p>
444             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
445             * <code>{2}</code>, etc.) are replaced with the arguments, following the
446             * standard Java {@link ResourceBundle} notion of index based substitution.
447             * </p>
448             *
449             * @param  request the request used to determine the current locale
450             * @param  pattern the key to look up in the current locale's resource file.
451             *         The key follows the standard Java resource specification.
452             * @param  arguments the arguments to be substituted into the pattern
453             * @param  translateArguments whether the arguments are translated
454             * @return the translated pattern, with the arguments substituted in for the
455             *         pattern's placeholders
456             */
457            @Override
458            public String format(
459                    HttpServletRequest request, String pattern, Object[] arguments,
460                    boolean translateArguments) {
461    
462                    if (PropsValues.TRANSLATIONS_DISABLED) {
463                            return pattern;
464                    }
465    
466                    String value = null;
467    
468                    try {
469                            pattern = get(request, pattern);
470    
471                            if (ArrayUtil.isNotEmpty(arguments)) {
472                                    pattern = _escapePattern(pattern);
473    
474                                    Object[] formattedArguments = new Object[arguments.length];
475    
476                                    for (int i = 0; i < arguments.length; i++) {
477                                            if (translateArguments) {
478                                                    formattedArguments[i] = get(
479                                                            request, arguments[i].toString());
480                                            }
481                                            else {
482                                                    formattedArguments[i] = arguments[i];
483                                            }
484                                    }
485    
486                                    MessageFormat messageFormat = decorateMessageFormat(
487                                            request, pattern, formattedArguments);
488    
489                                    value = messageFormat.format(formattedArguments);
490                            }
491                            else {
492                                    value = pattern;
493                            }
494                    }
495                    catch (Exception e) {
496                            if (_log.isWarnEnabled()) {
497                                    _log.warn(e, e);
498                            }
499                    }
500    
501                    return value;
502            }
503    
504            /**
505             * Returns the translated pattern using the locale or, if the locale is not
506             * available, the server's default locale.
507             *
508             * <p>
509             * The lookup is done on the portal's resource bundle. If a translation for
510             * a given key does not exist, this method returns the requested key as the
511             * translation.
512             * </p>
513             *
514             * <p>
515             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
516             * <code>{2}</code>, etc.) are replaced with the arguments, following the
517             * standard Java {@link ResourceBundle} notion of index based substitution.
518             * </p>
519             *
520             * @param  locale the locale to translate to
521             * @param  pattern the key to look up in the current locale's resource file.
522             *         The key follows the standard Java resource specification.
523             * @param  arguments the arguments to be substituted into the pattern
524             * @return the translated pattern, with the arguments substituted in for the
525             *         pattern's placeholders
526             */
527            @Override
528            public String format(
529                    Locale locale, String pattern, List<Object> arguments) {
530    
531                    return format(locale, pattern, arguments.toArray(), true);
532            }
533    
534            /**
535             * Returns the translated pattern using the locale or, if the locale is not
536             * available, the server's default locale.
537             *
538             * <p>
539             * The lookup is done on the portal's resource bundle. If a translation for
540             * a given key does not exist, this method returns the requested key as the
541             * translation.
542             * </p>
543             *
544             * <p>
545             * The substitute placeholder (e.g. <code>{0}</code>) is replaced with the
546             * argument, following the standard Java {@link ResourceBundle} notion of
547             * index based substitution.
548             * </p>
549             *
550             * @param  locale the locale to translate to
551             * @param  pattern the key to look up in the current locale's resource file.
552             *         The key follows the standard Java resource specification.
553             * @param  argument the argument to be substituted into the pattern
554             * @return the translated pattern, with the argument substituted in for the
555             *         pattern's placeholder
556             */
557            @Override
558            public String format(Locale locale, String pattern, Object argument) {
559                    return format(locale, pattern, new Object[] {argument}, true);
560            }
561    
562            /**
563             * Returns the translated pattern using the locale or, if the locale is not
564             * available, the server's default locale.
565             *
566             * <p>
567             * The lookup is done on the portal's resource bundle. If a translation for
568             * a given key does not exist, this method returns the requested key as the
569             * translation.
570             * </p>
571             *
572             * <p>
573             * The substitute placeholder (e.g. <code>{0}</code>) is replaced with the
574             * argument, following the standard Java {@link ResourceBundle} notion of
575             * index based substitution.
576             * </p>
577             *
578             * @param  locale the locale to translate to
579             * @param  pattern the key to look up in the current locale's resource file.
580             *         The key follows the standard Java resource specification.
581             * @param  argument the argument to be substituted into the pattern
582             * @param  translateArguments whether the argument is translated
583             * @return the translated pattern, with the argument substituted in for the
584             *         pattern's placeholder
585             */
586            @Override
587            public String format(
588                    Locale locale, String pattern, Object argument,
589                    boolean translateArguments) {
590    
591                    return format(
592                            locale, pattern, new Object[] {argument}, translateArguments);
593            }
594    
595            /**
596             * Returns the translated pattern using the locale or, if the locale is not
597             * available, the server's default locale.
598             *
599             * <p>
600             * The lookup is done on the portal's resource bundle. If a translation for
601             * a given key does not exist, this method returns the requested key as the
602             * translation.
603             * </p>
604             *
605             * <p>
606             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
607             * <code>{2}</code>, etc.) are replaced with the arguments, following the
608             * standard Java {@link ResourceBundle} notion of index based substitution.
609             * </p>
610             *
611             * @param  locale the locale to translate to
612             * @param  pattern the key to look up in the current locale's resource file.
613             *         The key follows the standard Java resource specification.
614             * @param  arguments the arguments to be substituted into the pattern
615             * @return the translated pattern, with the arguments substituted in for the
616             *         pattern's placeholders
617             */
618            @Override
619            public String format(Locale locale, String pattern, Object[] arguments) {
620                    return format(locale, pattern, arguments, true);
621            }
622    
623            /**
624             * Returns the translated pattern using the locale or, if the locale is not
625             * available, the server's default locale.
626             *
627             * <p>
628             * The lookup is done on the portal's resource bundle. If a translation for
629             * a given key does not exist, this method returns the requested key as the
630             * translation.
631             * </p>
632             *
633             * <p>
634             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
635             * <code>{2}</code>, etc.) are replaced with the arguments, following the
636             * standard Java {@link ResourceBundle} notion of index based substitution.
637             * </p>
638             *
639             * @param  locale the locale to translate to
640             * @param  pattern the key to look up in the current locale's resource file.
641             *         The key follows the standard Java resource specification.
642             * @param  arguments the arguments to be substituted into the pattern
643             * @param  translateArguments whether the arguments are translated
644             * @return the translated pattern, with the arguments substituted in for the
645             *         pattern's placeholders
646             */
647            @Override
648            public String format(
649                    Locale locale, String pattern, Object[] arguments,
650                    boolean translateArguments) {
651    
652                    if (PropsValues.TRANSLATIONS_DISABLED) {
653                            return pattern;
654                    }
655    
656                    String value = null;
657    
658                    try {
659                            pattern = get(locale, pattern);
660    
661                            if (ArrayUtil.isNotEmpty(arguments)) {
662                                    pattern = _escapePattern(pattern);
663    
664                                    Object[] formattedArguments = new Object[arguments.length];
665    
666                                    for (int i = 0; i < arguments.length; i++) {
667                                            if (translateArguments) {
668                                                    formattedArguments[i] = get(
669                                                            locale, arguments[i].toString());
670                                            }
671                                            else {
672                                                    formattedArguments[i] = arguments[i];
673                                            }
674                                    }
675    
676                                    MessageFormat messageFormat = decorateMessageFormat(
677                                            locale, pattern, formattedArguments);
678    
679                                    value = messageFormat.format(formattedArguments);
680                            }
681                            else {
682                                    value = pattern;
683                            }
684                    }
685                    catch (Exception e) {
686                            if (_log.isWarnEnabled()) {
687                                    _log.warn(e, e);
688                            }
689                    }
690    
691                    return value;
692            }
693    
694            /**
695             * Returns the translated pattern in the resource bundle or, if the resource
696             * bundle is not available, the untranslated key. If a translation for a
697             * given key does not exist, this method returns the requested key as the
698             * translation.
699             *
700             * <p>
701             * The substitute placeholder (e.g. <code>{0}</code>) is replaced with the
702             * argument, following the standard Java {@link ResourceBundle} notion of
703             * index based substitution.
704             * </p>
705             *
706             * @param  resourceBundle the requested key's resource bundle
707             * @param  pattern the key to look up in the resource bundle. The key
708             *         follows the standard Java resource specification.
709             * @param  argument the argument to be substituted into the pattern
710             * @return the translated pattern, with the argument substituted in for the
711             *         pattern's placeholder
712             */
713            @Override
714            public String format(
715                    ResourceBundle resourceBundle, String pattern, Object argument) {
716    
717                    return format(resourceBundle, pattern, new Object[] {argument}, true);
718            }
719    
720            /**
721             * Returns the translated pattern in the resource bundle or, if the resource
722             * bundle is not available, the untranslated key. If a translation for a
723             * given key does not exist, this method returns the requested key as the
724             * translation.
725             *
726             * <p>
727             * The substitute placeholder (e.g. <code>{0}</code>) is replaced with the
728             * argument, following the standard Java {@link ResourceBundle} notion of
729             * index based substitution.
730             * </p>
731             *
732             * @param  resourceBundle the requested key's resource bundle
733             * @param  pattern the key to look up in the resource bundle. The key
734             *         follows the standard Java resource specification.
735             * @param  argument the argument to be substituted into the pattern
736             * @param  translateArguments whether the argument is translated
737             * @return the translated pattern, with the argument substituted in for the
738             *         pattern's placeholder
739             */
740            @Override
741            public String format(
742                    ResourceBundle resourceBundle, String pattern, Object argument,
743                    boolean translateArguments) {
744    
745                    return format(
746                            resourceBundle, pattern, new Object[] {argument},
747                            translateArguments);
748            }
749    
750            /**
751             * Returns the translated pattern in the resource bundle or, if the resource
752             * bundle is not available, the untranslated key. If a translation for a
753             * given key does not exist, this method returns the requested key as the
754             * translation.
755             *
756             * <p>
757             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
758             * <code>{2}</code>, etc.) are replaced with the arguments, following the
759             * standard Java {@link ResourceBundle} notion of index based substitution.
760             * </p>
761             *
762             * @param  resourceBundle the requested key's resource bundle
763             * @param  pattern the key to look up in the resource bundle. The key
764             *         follows the standard Java resource specification.
765             * @param  arguments the arguments to be substituted into the pattern
766             * @return the translated pattern, with the arguments substituted in for the
767             *         pattern's placeholder
768             */
769            @Override
770            public String format(
771                    ResourceBundle resourceBundle, String pattern, Object[] arguments) {
772    
773                    return format(resourceBundle, pattern, arguments, true);
774            }
775    
776            /**
777             * Returns the translated pattern in the resource bundle or, if the resource
778             * bundle is not available, the untranslated key. If a translation for a
779             * given key does not exist, this method returns the requested key as the
780             * translation.
781             *
782             * <p>
783             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
784             * <code>{2}</code>, etc.) are replaced with the arguments, following the
785             * standard Java {@link ResourceBundle} notion of index based substitution.
786             * </p>
787             *
788             * @param  resourceBundle the requested key's resource bundle
789             * @param  pattern the key to look up in the resource bundle. The key
790             *         follows the standard Java resource specification.
791             * @param  arguments the arguments to be substituted into the pattern
792             * @param  translateArguments whether the arguments are translated
793             * @return the translated pattern, with the arguments substituted in for the
794             *         pattern's placeholder
795             */
796            @Override
797            public String format(
798                    ResourceBundle resourceBundle, String pattern, Object[] arguments,
799                    boolean translateArguments) {
800    
801                    if (PropsValues.TRANSLATIONS_DISABLED) {
802                            return pattern;
803                    }
804    
805                    String value = null;
806    
807                    try {
808                            pattern = get(resourceBundle, pattern);
809    
810                            if (ArrayUtil.isNotEmpty(arguments)) {
811                                    pattern = _escapePattern(pattern);
812    
813                                    Object[] formattedArguments = new Object[arguments.length];
814    
815                                    for (int i = 0; i < arguments.length; i++) {
816                                            if (translateArguments) {
817                                                    formattedArguments[i] = get(
818                                                            resourceBundle, arguments[i].toString());
819                                            }
820                                            else {
821                                                    formattedArguments[i] = arguments[i];
822                                            }
823                                    }
824    
825                                    MessageFormat messageFormat = decorateMessageFormat(
826                                            resourceBundle.getLocale(), pattern, formattedArguments);
827    
828                                    value = messageFormat.format(formattedArguments);
829                            }
830                            else {
831                                    value = pattern;
832                            }
833                    }
834                    catch (Exception e) {
835                            if (_log.isWarnEnabled()) {
836                                    _log.warn(e, e);
837                            }
838                    }
839    
840                    return value;
841            }
842    
843            /**
844             * Returns the key's translation from the portlet configuration, or from the
845             * portal's resource bundle if the portlet configuration is unavailable.
846             *
847             * @param  request the request used to determine the key's context and
848             *         locale
849             * @param  resourceBundle the requested key's resource bundle
850             * @param  key the translation key
851             * @return the key's translation, or the key if the translation is
852             *         unavailable
853             */
854            @Override
855            public String get(
856                    HttpServletRequest request, ResourceBundle resourceBundle, String key) {
857    
858                    return get(request, resourceBundle, key, key);
859            }
860    
861            @Override
862            public String get(
863                    HttpServletRequest request, ResourceBundle resourceBundle, String key,
864                    String defaultValue) {
865    
866                    String value = _get(resourceBundle, key);
867    
868                    if (value != null) {
869                            return value;
870                    }
871    
872                    return get(request, key, defaultValue);
873            }
874    
875            @Override
876            public String get(HttpServletRequest request, String key) {
877                    return get(request, key, key);
878            }
879    
880            /**
881             * Returns the key's translation from the portlet configuration, or from the
882             * portal's resource bundle if the portlet configuration is unavailable.
883             *
884             * @param  request the request used to determine the key's context and
885             *         locale
886             * @param  key the translation key
887             * @param  defaultValue the value to return if there is no matching
888             *         translation
889             * @return the key's translation, or the default value if the translation is
890             *         unavailable
891             */
892            @Override
893            public String get(
894                    HttpServletRequest request, String key, String defaultValue) {
895    
896                    if ((request == null) || (key == null)) {
897                            return defaultValue;
898                    }
899    
900                    PortletConfig portletConfig = (PortletConfig)request.getAttribute(
901                            JavaConstants.JAVAX_PORTLET_CONFIG);
902    
903                    Locale locale = _getLocale(request);
904    
905                    if (portletConfig == null) {
906                            return get(locale, key, defaultValue);
907                    }
908    
909                    ResourceBundle resourceBundle = portletConfig.getResourceBundle(locale);
910    
911                    if (resourceBundle.containsKey(key)) {
912                            return _get(resourceBundle, key);
913                    }
914    
915                    return get(locale, key, defaultValue);
916            }
917    
918            /**
919             * Returns the key's translation from the portal's resource bundle.
920             *
921             * @param  locale the key's locale
922             * @param  key the translation key
923             * @return the key's translation
924             */
925            @Override
926            public String get(Locale locale, String key) {
927                    return get(locale, key, key);
928            }
929    
930            /**
931             * Returns the key's translation from the portal's resource bundle.
932             *
933             * @param  locale the key's locale
934             * @param  key the translation key
935             * @param  defaultValue the value to return if there is no matching
936             *         translation
937             * @return the key's translation, or the default value if the translation is
938             *         unavailable
939             */
940            @Override
941            public String get(Locale locale, String key, String defaultValue) {
942                    if (PropsValues.TRANSLATIONS_DISABLED) {
943                            return key;
944                    }
945    
946                    if ((locale == null) || (key == null)) {
947                            return defaultValue;
948                    }
949    
950                    String value = LanguageResources.getMessage(locale, key);
951    
952                    if (value != null) {
953                            return LanguageResources.fixValue(value);
954                    }
955    
956                    if (value == null) {
957                            if ((key.length() > 0) &&
958                                    (key.charAt(key.length() - 1) == CharPool.CLOSE_BRACKET)) {
959    
960                                    int pos = key.lastIndexOf(CharPool.OPEN_BRACKET);
961    
962                                    if (pos != -1) {
963                                            key = key.substring(0, pos);
964    
965                                            return get(locale, key, defaultValue);
966                                    }
967                            }
968                    }
969    
970                    return defaultValue;
971            }
972    
973            /**
974             * Returns the key's translation from the resource bundle.
975             *
976             * @param  resourceBundle the requested key's resource bundle
977             * @param  key the translation key
978             * @return the key's translation
979             */
980            @Override
981            public String get(ResourceBundle resourceBundle, String key) {
982                    return get(resourceBundle, key, key);
983            }
984    
985            /**
986             * Returns the key's translation from the resource bundle.
987             *
988             * @param  resourceBundle the requested key's resource bundle
989             * @param  key the translation key
990             * @param  defaultValue the value to return if there is no matching
991             *         translation
992             * @return the key's translation, or the default value if the translation is
993             *         unavailable
994             */
995            @Override
996            public String get(
997                    ResourceBundle resourceBundle, String key, String defaultValue) {
998    
999                    String value = _get(resourceBundle, key);
1000    
1001                    if (value != null) {
1002                            return value;
1003                    }
1004    
1005                    return defaultValue;
1006            }
1007    
1008            /**
1009             * Returns the locales configured for the portal. Locales can be configured
1010             * in <code>portal.properties</code> using the <code>locales</code> and
1011             * <code>locales.enabled</code> keys.
1012             *
1013             * @return the locales configured for the portal
1014             */
1015            @Override
1016            public Set<Locale> getAvailableLocales() {
1017                    CompanyLocalesBag companyLocalesBag = _getCompanyLocalesBag();
1018    
1019                    return companyLocalesBag.getAvailableLocales();
1020            }
1021    
1022            @Override
1023            public Set<Locale> getAvailableLocales(long groupId) {
1024                    if (groupId <= 0) {
1025                            return getAvailableLocales();
1026                    }
1027    
1028                    try {
1029                            if (isInheritLocales(groupId)) {
1030                                    return getAvailableLocales();
1031                            }
1032                    }
1033                    catch (Exception e) {
1034                    }
1035    
1036                    Map<String, Locale> groupLanguageIdLocalesMap =
1037                            _getGroupLanguageIdLocalesMap(groupId);
1038    
1039                    return new HashSet<>(groupLanguageIdLocalesMap.values());
1040            }
1041    
1042            @Override
1043            public String getBCP47LanguageId(HttpServletRequest request) {
1044                    Locale locale = PortalUtil.getLocale(request);
1045    
1046                    return getBCP47LanguageId(locale);
1047            }
1048    
1049            @Override
1050            public String getBCP47LanguageId(Locale locale) {
1051                    return LocaleUtil.toBCP47LanguageId(locale);
1052            }
1053    
1054            @Override
1055            public String getBCP47LanguageId(PortletRequest portletRequest) {
1056                    Locale locale = PortalUtil.getLocale(portletRequest);
1057    
1058                    return getBCP47LanguageId(locale);
1059            }
1060    
1061            /**
1062             * Returns the language ID that the request is served with. The language ID
1063             * is returned as a language code (e.g. <code>en</code>) or a specific
1064             * variant (e.g. <code>en_GB</code>).
1065             *
1066             * @param  request the request used to determine the language ID
1067             * @return the language ID that the request is served with
1068             */
1069            @Override
1070            public String getLanguageId(HttpServletRequest request) {
1071                    String languageId = ParamUtil.getString(request, "languageId");
1072    
1073                    if (Validator.isNotNull(languageId)) {
1074                            CompanyLocalesBag companyLocalesBag = _getCompanyLocalesBag();
1075    
1076                            if (companyLocalesBag.containsLanguageCode(languageId) ||
1077                                    companyLocalesBag.containsLanguageId(languageId)) {
1078    
1079                                    return languageId;
1080                            }
1081                    }
1082    
1083                    Locale locale = PortalUtil.getLocale(request);
1084    
1085                    return getLanguageId(locale);
1086            }
1087    
1088            /**
1089             * Returns the language ID from the locale. The language ID is returned as a
1090             * language code (e.g. <code>en</code>) or a specific variant (e.g.
1091             * <code>en_GB</code>).
1092             *
1093             * @param  locale the locale used to determine the language ID
1094             * @return the language ID from the locale
1095             */
1096            @Override
1097            public String getLanguageId(Locale locale) {
1098                    return LocaleUtil.toLanguageId(locale);
1099            }
1100    
1101            /**
1102             * Returns the language ID that the {@link PortletRequest} is served with.
1103             * The language ID is returned as a language code (e.g. <code>en</code>) or
1104             * a specific variant (e.g. <code>en_GB</code>).
1105             *
1106             * @param  portletRequest the portlet request used to determine the language
1107             *         ID
1108             * @return the language ID that the portlet request is served with
1109             */
1110            @Override
1111            public String getLanguageId(PortletRequest portletRequest) {
1112                    HttpServletRequest request = PortalUtil.getHttpServletRequest(
1113                            portletRequest);
1114    
1115                    return getLanguageId(request);
1116            }
1117    
1118            @Override
1119            public Locale getLocale(long groupId, String languageCode) {
1120                    Map<String, Locale> groupLanguageCodeLocalesMap =
1121                            _getGroupLanguageCodeLocalesMap(groupId);
1122    
1123                    return groupLanguageCodeLocalesMap.get(languageCode);
1124            }
1125    
1126            /**
1127             * Returns the locale associated with the language code.
1128             *
1129             * @param  languageCode the code representation of a language (e.g.
1130             *         <code>en</code> and <code>en_GB</code>)
1131             * @return the locale associated with the language code
1132             */
1133            @Override
1134            public Locale getLocale(String languageCode) {
1135                    CompanyLocalesBag companyLocalesBag = _getCompanyLocalesBag();
1136    
1137                    return companyLocalesBag.getByLanguageCode(languageCode);
1138            }
1139    
1140            @Override
1141            public ResourceBundleLoader getPortalResourceBundleLoader() {
1142                    return LanguageResources.RESOURCE_BUNDLE_LOADER;
1143            }
1144    
1145            @Override
1146            public Set<Locale> getSupportedLocales() {
1147                    CompanyLocalesBag companyLocalesBag = _getCompanyLocalesBag();
1148    
1149                    return companyLocalesBag._supportedLocalesSet;
1150            }
1151    
1152            /**
1153             * Returns an exact localized description of the time interval (in
1154             * milliseconds) in the largest unit possible.
1155             *
1156             * <p>
1157             * For example, the following time intervals would be converted to the
1158             * following time descriptions, using the English locale:
1159             * </p>
1160             *
1161             * <ul>
1162             * <li>
1163             * 1000 = 1 Second
1164             * </li>
1165             * <li>
1166             * 1001 = 1001 Milliseconds
1167             * </li>
1168             * <li>
1169             * 86400000 = 1 Day
1170             * </li>
1171             * <li>
1172             * 86401000 = 86401 Seconds
1173             * </li>
1174             * </ul>
1175             *
1176             * @param  request the request used to determine the current locale
1177             * @param  milliseconds the time interval in milliseconds to describe
1178             * @return an exact localized description of the time interval in the
1179             *         largest unit possible
1180             */
1181            @Override
1182            public String getTimeDescription(
1183                    HttpServletRequest request, long milliseconds) {
1184    
1185                    return getTimeDescription(request, milliseconds, false);
1186            }
1187    
1188            /**
1189             * Returns an approximate or exact localized description of the time
1190             * interval (in milliseconds) in the largest unit possible.
1191             *
1192             * <p>
1193             * Approximate descriptions round the time to the largest possible unit and
1194             * ignores the rest. For example, using the English locale:
1195             * </p>
1196             *
1197             * <ul>
1198             * <li>
1199             * Any time interval 1000-1999 = 1 Second
1200             * </li>
1201             * <li>
1202             * Any time interval 86400000-172799999 = 1 Day
1203             * </li>
1204             * </ul>
1205             *
1206             * <p>
1207             * Otherwise, exact descriptions would follow a similar conversion pattern
1208             * as below:
1209             * </p>
1210             *
1211             * <ul>
1212             * <li>
1213             * 1000 = 1 Second
1214             * </li>
1215             * <li>
1216             * 1001 = 1001 Milliseconds
1217             * </li>
1218             * <li>
1219             * 86400000 = 1 Day
1220             * </li>
1221             * <li>
1222             * 86401000 = 86401 Seconds
1223             * </li>
1224             * </ul>
1225             *
1226             * @param  request the request used to determine the current locale
1227             * @param  milliseconds the time interval in milliseconds to describe
1228             * @param  approximate whether the time description is approximate
1229             * @return a localized description of the time interval in the largest unit
1230             *         possible
1231             */
1232            @Override
1233            public String getTimeDescription(
1234                    HttpServletRequest request, long milliseconds, boolean approximate) {
1235    
1236                    String description = Time.getDescription(milliseconds, approximate);
1237    
1238                    String value = null;
1239    
1240                    try {
1241                            int pos = description.indexOf(CharPool.SPACE);
1242    
1243                            String x = description.substring(0, pos);
1244    
1245                            value = x.concat(StringPool.SPACE).concat(
1246                                    get(
1247                                            request,
1248                                            StringUtil.toLowerCase(
1249                                                    description.substring(pos + 1, description.length()))));
1250                    }
1251                    catch (Exception e) {
1252                            if (_log.isWarnEnabled()) {
1253                                    _log.warn(e, e);
1254                            }
1255                    }
1256    
1257                    return value;
1258            }
1259    
1260            /**
1261             * Returns an exact localized description of the time interval (in
1262             * milliseconds) in the largest unit possible.
1263             *
1264             * <p>
1265             * For example, the following time intervals would be converted to the
1266             * following time descriptions, using the English locale:
1267             * </p>
1268             *
1269             * <ul>
1270             * <li>
1271             * 1000 = 1 Second
1272             * </li>
1273             * <li>
1274             * 1001 = 1001 Milliseconds
1275             * </li>
1276             * <li>
1277             * 86400000 = 1 Day
1278             * </li>
1279             * <li>
1280             * 86401000 = 86401 Seconds
1281             * </li>
1282             * </ul>
1283             *
1284             * @param  request the request used to determine the current locale
1285             * @param  milliseconds the time interval in milliseconds to describe
1286             * @return an exact localized description of the time interval in the
1287             *         largest unit possible
1288             */
1289            @Override
1290            public String getTimeDescription(
1291                    HttpServletRequest request, Long milliseconds) {
1292    
1293                    return getTimeDescription(request, milliseconds.longValue());
1294            }
1295    
1296            /**
1297             * Returns an exact localized description of the time interval (in
1298             * milliseconds) in the largest unit possible.
1299             *
1300             * <p>
1301             * For example, the following time intervals would be converted to the
1302             * following time descriptions, using the English locale:
1303             * </p>
1304             *
1305             * <ul>
1306             * <li>
1307             * 1000 = 1 Second
1308             * </li>
1309             * <li>
1310             * 1001 = 1001 Milliseconds
1311             * </li>
1312             * <li>
1313             * 86400000 = 1 Day
1314             * </li>
1315             * <li>
1316             * 86401000 = 86401 Seconds
1317             * </li>
1318             * </ul>
1319             *
1320             * @param  locale the locale used to determine the language
1321             * @param  milliseconds the time interval in milliseconds to describe
1322             * @return an exact localized description of the time interval in the
1323             *         largest unit possible
1324             */
1325            @Override
1326            public String getTimeDescription(Locale locale, long milliseconds) {
1327                    return getTimeDescription(locale, milliseconds, false);
1328            }
1329    
1330            /**
1331             * Returns an approximate or exact localized description of the time
1332             * interval (in milliseconds) in the largest unit possible.
1333             *
1334             * <p>
1335             * Approximate descriptions round the time to the largest possible unit and
1336             * ignores the rest. For example, using the English locale:
1337             * </p>
1338             *
1339             * <ul>
1340             * <li>
1341             * Any time interval 1000-1999 = 1 Second
1342             * </li>
1343             * <li>
1344             * Any time interval 86400000-172799999 = 1 Day
1345             * </li>
1346             * </ul>
1347             *
1348             * <p>
1349             * Otherwise, exact descriptions would follow a similar conversion pattern
1350             * as below:
1351             * </p>
1352             *
1353             * <ul>
1354             * <li>
1355             * 1000 = 1 Second
1356             * </li>
1357             * <li>
1358             * 1001 = 1001 Milliseconds
1359             * </li>
1360             * <li>
1361             * 86400000 = 1 Day
1362             * </li>
1363             * <li>
1364             * 86401000 = 86401 Seconds
1365             * </li>
1366             * </ul>
1367             *
1368             * @param  locale the locale used to determine the language
1369             * @param  milliseconds the time interval in milliseconds to describe
1370             * @param  approximate whether the time description is approximate
1371             * @return a localized description of the time interval in the largest unit
1372             *         possible
1373             */
1374            @Override
1375            public String getTimeDescription(
1376                    Locale locale, long milliseconds, boolean approximate) {
1377    
1378                    String description = Time.getDescription(milliseconds, approximate);
1379    
1380                    String value = null;
1381    
1382                    try {
1383                            int pos = description.indexOf(CharPool.SPACE);
1384    
1385                            String x = description.substring(0, pos);
1386    
1387                            value = x.concat(StringPool.SPACE).concat(
1388                                    get(
1389                                            locale,
1390                                            StringUtil.toLowerCase(
1391                                                    description.substring(pos + 1, description.length()))));
1392                    }
1393                    catch (Exception e) {
1394                            if (_log.isWarnEnabled()) {
1395                                    _log.warn(e, e);
1396                            }
1397                    }
1398    
1399                    return value;
1400            }
1401    
1402            /**
1403             * Returns an exact localized description of the time interval (in
1404             * milliseconds) in the largest unit possible.
1405             *
1406             * <p>
1407             * For example, the following time intervals would be converted to the
1408             * following time descriptions, using the English locale:
1409             * </p>
1410             *
1411             * <ul>
1412             * <li>
1413             * 1000 = 1 Second
1414             * </li>
1415             * <li>
1416             * 1001 = 1001 Milliseconds
1417             * </li>
1418             * <li>
1419             * 86400000 = 1 Day
1420             * </li>
1421             * <li>
1422             * 86401000 = 86401 Seconds
1423             * </li>
1424             * </ul>
1425             *
1426             * @param  locale the locale used to determine the language
1427             * @param  milliseconds the time interval in milliseconds to describe
1428             * @return an exact localized description of the time interval in the
1429             *         largest unit possible
1430             */
1431            @Override
1432            public String getTimeDescription(Locale locale, Long milliseconds) {
1433                    return getTimeDescription(locale, milliseconds.longValue());
1434            }
1435    
1436            @Override
1437            public void init() {
1438                    _companyLocalesBags.clear();
1439            }
1440    
1441            /**
1442             * Returns <code>true</code> if the language code is configured to be
1443             * available. Locales can be configured in <code>portal.properties</code>
1444             * using the <code>locales</code> and <code>locales.enabled</code> keys.
1445             *
1446             * @param  languageCode the code representation of a language (e.g.
1447             *         <code>en</code> and <code>en_GB</code>) to search for
1448             * @return <code>true</code> if the language code is configured to be
1449             *         available; <code>false</code> otherwise
1450             */
1451            @Override
1452            public boolean isAvailableLanguageCode(String languageCode) {
1453                    CompanyLocalesBag companyLocalesBag = _getCompanyLocalesBag();
1454    
1455                    return companyLocalesBag.containsLanguageCode(languageCode);
1456            }
1457    
1458            /**
1459             * Returns <code>true</code> if the locale is configured to be available.
1460             * Locales can be configured in <code>portal.properties</code> using the
1461             * <code>locales</code> and <code>locales.enabled</code> keys.
1462             *
1463             * @param  locale the locale to search for
1464             * @return <code>true</code> if the locale is configured to be available;
1465             *         <code>false</code> otherwise
1466             */
1467            @Override
1468            public boolean isAvailableLocale(Locale locale) {
1469                    if (locale == null) {
1470                            return false;
1471                    }
1472    
1473                    return isAvailableLocale(LocaleUtil.toLanguageId(locale));
1474            }
1475    
1476            /**
1477             * Returns <code>true</code> if the locale is configured to be available in
1478             * the group.
1479             *
1480             * @param  groupId the primary key of the group
1481             * @param  locale the locale to search for
1482             * @return <code>true</code> if the locale is configured to be available in
1483             *         the group; <code>false</code> otherwise
1484             */
1485            @Override
1486            public boolean isAvailableLocale(long groupId, Locale locale) {
1487                    if (locale == null) {
1488                            return false;
1489                    }
1490    
1491                    return isAvailableLocale(groupId, LocaleUtil.toLanguageId(locale));
1492            }
1493    
1494            /**
1495             * Returns <code>true</code> if the language ID is configured to be
1496             * available in the group.
1497             *
1498             * @param  groupId the primary key of the group
1499             * @param  languageId the language ID to search for
1500             * @return <code>true</code> if the language ID is configured to be
1501             *         available in the group; <code>false</code> otherwise
1502             */
1503            @Override
1504            public boolean isAvailableLocale(long groupId, String languageId) {
1505                    if (groupId <= 0) {
1506                            return isAvailableLocale(languageId);
1507                    }
1508    
1509                    try {
1510                            if (isInheritLocales(groupId)) {
1511                                    return isAvailableLocale(languageId);
1512                            }
1513                    }
1514                    catch (Exception e) {
1515                    }
1516    
1517                    Map<String, Locale> groupLanguageIdLocalesMap =
1518                            _getGroupLanguageIdLocalesMap(groupId);
1519    
1520                    return groupLanguageIdLocalesMap.containsKey(languageId);
1521            }
1522    
1523            /**
1524             * Returns <code>true</code> if the language ID is configured to be
1525             * available.
1526             *
1527             * @param  languageId the language ID to search for
1528             * @return <code>true</code> if the language ID is configured to be
1529             *         available; <code>false</code> otherwise
1530             */
1531            @Override
1532            public boolean isAvailableLocale(String languageId) {
1533                    CompanyLocalesBag companyLocalesBag = _getCompanyLocalesBag();
1534    
1535                    return companyLocalesBag.containsLanguageId(languageId);
1536            }
1537    
1538            /**
1539             * Returns <code>true</code> if the locale is configured to be a beta
1540             * language.
1541             *
1542             * @param  locale the locale to search for
1543             * @return <code>true</code> if the locale is configured to be a beta
1544             *         language; <code>false</code> otherwise
1545             */
1546            @Override
1547            public boolean isBetaLocale(Locale locale) {
1548                    CompanyLocalesBag companyLocalesBag = _getCompanyLocalesBag();
1549    
1550                    return companyLocalesBag.isBetaLocale(locale);
1551            }
1552    
1553            @Override
1554            public boolean isDuplicateLanguageCode(String languageCode) {
1555                    CompanyLocalesBag companyLocalesBag = _getCompanyLocalesBag();
1556    
1557                    return companyLocalesBag.isDuplicateLanguageCode(languageCode);
1558            }
1559    
1560            @Override
1561            public boolean isInheritLocales(long groupId) throws PortalException {
1562                    Group group = GroupLocalServiceUtil.getGroup(groupId);
1563    
1564                    if (group.isStagingGroup()) {
1565                            group = group.getLiveGroup();
1566                    }
1567    
1568                    if (!group.isSite() || group.isCompany()) {
1569                            return true;
1570                    }
1571    
1572                    return GetterUtil.getBoolean(
1573                            group.getTypeSettingsProperty(
1574                                    GroupConstants.TYPE_SETTINGS_KEY_INHERIT_LOCALES),
1575                            true);
1576            }
1577    
1578            @Override
1579            public String process(
1580                    ResourceBundle resourceBundle, Locale locale, String content) {
1581    
1582                    StringBundler sb = new StringBundler();
1583    
1584                    Matcher matcher = _pattern.matcher(content);
1585    
1586                    int x = 0;
1587    
1588                    while (matcher.find()) {
1589                            int y = matcher.start(0);
1590    
1591                            String key = matcher.group(1);
1592    
1593                            sb.append(content.substring(x, y));
1594                            sb.append(StringPool.APOSTROPHE);
1595    
1596                            String value = get(resourceBundle, key);
1597    
1598                            sb.append(HtmlUtil.escapeJS(value));
1599                            sb.append(StringPool.APOSTROPHE);
1600    
1601                            x = matcher.end(0);
1602                    }
1603    
1604                    sb.append(content.substring(x));
1605    
1606                    return sb.toString();
1607            }
1608    
1609            @Override
1610            public void resetAvailableGroupLocales(long groupId) {
1611                    _resetAvailableGroupLocales(groupId);
1612            }
1613    
1614            @Override
1615            public void resetAvailableLocales(long companyId) {
1616                    _resetAvailableLocales(companyId);
1617            }
1618    
1619            @Override
1620            public void updateCookie(
1621                    HttpServletRequest request, HttpServletResponse response,
1622                    Locale locale) {
1623    
1624                    String languageId = LocaleUtil.toLanguageId(locale);
1625    
1626                    Cookie languageIdCookie = new Cookie(
1627                            CookieKeys.GUEST_LANGUAGE_ID, languageId);
1628    
1629                    languageIdCookie.setPath(StringPool.SLASH);
1630                    languageIdCookie.setMaxAge(CookieKeys.MAX_AGE);
1631    
1632                    CookieKeys.addCookie(request, response, languageIdCookie);
1633            }
1634    
1635            protected MessageFormat decorateMessageFormat(
1636                    HttpServletRequest request, String pattern,
1637                    Object[] formattedArguments) {
1638    
1639                    Locale locale = _getLocale(request);
1640    
1641                    return decorateMessageFormat(locale, pattern, formattedArguments);
1642            }
1643    
1644            protected MessageFormat decorateMessageFormat(
1645                    Locale locale, String pattern, Object[] formattedArguments) {
1646    
1647                    if (locale == null) {
1648                            locale = LocaleUtil.getDefault();
1649                    }
1650    
1651                    MessageFormat messageFormat = new MessageFormat(pattern, locale);
1652    
1653                    for (int i = 0; i < formattedArguments.length; i++) {
1654                            Object formattedArgument = formattedArguments[i];
1655    
1656                            if (formattedArgument instanceof Number) {
1657                                    messageFormat.setFormat(i, NumberFormat.getInstance(locale));
1658                            }
1659                    }
1660    
1661                    return messageFormat;
1662            }
1663    
1664            private static CompanyLocalesBag _getCompanyLocalesBag() {
1665                    Long companyId = CompanyThreadLocal.getCompanyId();
1666    
1667                    CompanyLocalesBag companyLocalesBag = _companyLocalesBags.get(
1668                            companyId);
1669    
1670                    if (companyLocalesBag == null) {
1671                            companyLocalesBag = new CompanyLocalesBag(companyId);
1672    
1673                            _companyLocalesBags.put(companyId, companyLocalesBag);
1674                    }
1675    
1676                    return companyLocalesBag;
1677            }
1678    
1679            private ObjectValuePair<HashMap<String, Locale>, HashMap<String, Locale>>
1680                    _createGroupLocales(long groupId) {
1681    
1682                    String[] languageIds = PropsValues.LOCALES_ENABLED;
1683    
1684                    try {
1685                            Group group = GroupLocalServiceUtil.getGroup(groupId);
1686    
1687                            UnicodeProperties typeSettingsProperties =
1688                                    group.getTypeSettingsProperties();
1689    
1690                            languageIds = StringUtil.split(
1691                                    typeSettingsProperties.getProperty(PropsKeys.LOCALES));
1692                    }
1693                    catch (Exception e) {
1694                    }
1695    
1696                    HashMap<String, Locale> groupLanguageCodeLocalesMap = new HashMap<>();
1697                    HashMap<String, Locale> groupLanguageIdLocalesMap = new HashMap<>();
1698    
1699                    for (String languageId : languageIds) {
1700                            Locale locale = LocaleUtil.fromLanguageId(languageId, false);
1701    
1702                            String languageCode = languageId;
1703    
1704                            int pos = languageId.indexOf(CharPool.UNDERLINE);
1705    
1706                            if (pos > 0) {
1707                                    languageCode = languageId.substring(0, pos);
1708                            }
1709    
1710                            if (!groupLanguageCodeLocalesMap.containsKey(languageCode)) {
1711                                    groupLanguageCodeLocalesMap.put(languageCode, locale);
1712                            }
1713    
1714                            groupLanguageIdLocalesMap.put(languageId, locale);
1715                    }
1716    
1717                    _groupLanguageCodeLocalesMapMap.put(
1718                            groupId, groupLanguageCodeLocalesMap);
1719                    _groupLanguageIdLocalesMap.put(groupId, groupLanguageIdLocalesMap);
1720    
1721                    return new ObjectValuePair<>(
1722                            groupLanguageCodeLocalesMap, groupLanguageIdLocalesMap);
1723            }
1724    
1725            private String _escapePattern(String pattern) {
1726                    return StringUtil.replace(
1727                            pattern, CharPool.APOSTROPHE, StringPool.DOUBLE_APOSTROPHE);
1728            }
1729    
1730            private String _get(ResourceBundle resourceBundle, String key) {
1731                    if (PropsValues.TRANSLATIONS_DISABLED) {
1732                            return key;
1733                    }
1734    
1735                    if ((resourceBundle == null) || (key == null)) {
1736                            return null;
1737                    }
1738    
1739                    String value = ResourceBundleUtil.getString(resourceBundle, key);
1740    
1741                    if (value != null) {
1742                            return LanguageResources.fixValue(value);
1743                    }
1744    
1745                    if ((key.length() > 0) &&
1746                            (key.charAt(key.length() - 1) == CharPool.CLOSE_BRACKET)) {
1747    
1748                            int pos = key.lastIndexOf(CharPool.OPEN_BRACKET);
1749    
1750                            if (pos != -1) {
1751                                    key = key.substring(0, pos);
1752    
1753                                    return _get(resourceBundle, key);
1754                            }
1755                    }
1756    
1757                    return null;
1758            }
1759    
1760            private Map<String, Locale> _getGroupLanguageCodeLocalesMap(long groupId) {
1761                    Map<String, Locale> groupLanguageCodeLocalesMap =
1762                            _groupLanguageCodeLocalesMapMap.get(groupId);
1763    
1764                    if (groupLanguageCodeLocalesMap == null) {
1765                            ObjectValuePair<HashMap<String, Locale>, HashMap<String, Locale>>
1766                                    objectValuePair = _createGroupLocales(groupId);
1767    
1768                            groupLanguageCodeLocalesMap = objectValuePair.getKey();
1769                    }
1770    
1771                    return groupLanguageCodeLocalesMap;
1772            }
1773    
1774            private Map<String, Locale> _getGroupLanguageIdLocalesMap(long groupId) {
1775                    Map<String, Locale> groupLanguageIdLocalesMap =
1776                            _groupLanguageIdLocalesMap.get(groupId);
1777    
1778                    if (groupLanguageIdLocalesMap == null) {
1779                            ObjectValuePair<HashMap<String, Locale>, HashMap<String, Locale>>
1780                                    objectValuePair = _createGroupLocales(groupId);
1781    
1782                            groupLanguageIdLocalesMap = objectValuePair.getValue();
1783                    }
1784    
1785                    return groupLanguageIdLocalesMap;
1786            }
1787    
1788            private Locale _getLocale(HttpServletRequest request) {
1789                    Locale locale = null;
1790    
1791                    ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
1792                            WebKeys.THEME_DISPLAY);
1793    
1794                    if (themeDisplay != null) {
1795                            locale = themeDisplay.getLocale();
1796                    }
1797                    else {
1798                            locale = request.getLocale();
1799    
1800                            if (!isAvailableLocale(locale)) {
1801                                    locale = LocaleUtil.getDefault();
1802                            }
1803                    }
1804    
1805                    return locale;
1806            }
1807    
1808            private void _resetAvailableGroupLocales(long groupId) {
1809                    _groupLocalesPortalCache.remove(groupId);
1810            }
1811    
1812            private void _resetAvailableLocales(long companyId) {
1813                    _companyLocalesPortalCache.remove(companyId);
1814            }
1815    
1816            private static final String _COMPANY_LOCALES_PORTAL_CACHE_NAME =
1817                    LanguageImpl.class + "._companyLocalesPortalCache";
1818    
1819            private static final String _GROUP_LOCALES_PORTAL_CACHE_NAME =
1820                    LanguageImpl.class + "._groupLocalesPortalCache";
1821    
1822            private static final Log _log = LogFactoryUtil.getLog(LanguageImpl.class);
1823    
1824            private static final Map<Long, CompanyLocalesBag> _companyLocalesBags =
1825                    new ConcurrentHashMap<>();
1826            private static PortalCache<Long, Serializable> _companyLocalesPortalCache;
1827            private static PortalCache<Long, Serializable> _groupLocalesPortalCache;
1828            private static final Pattern _pattern = Pattern.compile(
1829                    "Liferay\\.Language\\.get\\([\"']([^)]+)[\"']\\)");
1830    
1831            private static final Synchronizer<Long, Serializable> _removeSynchronizer =
1832                    new Synchronizer<Long, Serializable>() {
1833    
1834                            @Override
1835                            public void onSynchronize(
1836                                    Map<? extends Long, ? extends Serializable> map, Long key,
1837                                    Serializable value, int timeToLive) {
1838    
1839                                    map.remove(key);
1840                            }
1841    
1842                    };
1843    
1844            private final Map<Long, HashMap<String, Locale>>
1845                    _groupLanguageCodeLocalesMapMap = new ConcurrentHashMap<>();
1846            private final Map<Long, HashMap<String, Locale>>
1847                    _groupLanguageIdLocalesMap = new ConcurrentHashMap<>();
1848    
1849            private static class CompanyLocalesBag implements Serializable {
1850    
1851                    public boolean containsLanguageCode(String languageCode) {
1852                            return _languageCodeLocalesMap.containsKey(languageCode);
1853                    }
1854    
1855                    public boolean containsLanguageId(String languageId) {
1856                            return _languageIdLocalesMap.containsKey(languageId);
1857                    }
1858    
1859                    public Set<Locale> getAvailableLocales() {
1860                            return _availableLocales;
1861                    }
1862    
1863                    public Locale getByLanguageCode(String languageCode) {
1864                            return _languageCodeLocalesMap.get(languageCode);
1865                    }
1866    
1867                    public boolean isBetaLocale(Locale locale) {
1868                            return _localesBetaSet.contains(locale);
1869                    }
1870    
1871                    public boolean isDuplicateLanguageCode(String languageCode) {
1872                            return _duplicateLanguageCodes.contains(languageCode);
1873                    }
1874    
1875                    private CompanyLocalesBag(long companyId) {
1876                            String[] languageIds = PropsValues.LOCALES;
1877    
1878                            if (companyId != CompanyConstants.SYSTEM) {
1879                                    try {
1880                                            languageIds = PrefsPropsUtil.getStringArray(
1881                                                    companyId, PropsKeys.LOCALES, StringPool.COMMA,
1882                                                    PropsValues.LOCALES_ENABLED);
1883                                    }
1884                                    catch (SystemException se) {
1885                                            languageIds = PropsValues.LOCALES_ENABLED;
1886                                    }
1887                            }
1888    
1889                            for (String languageId : languageIds) {
1890                                    Locale locale = LocaleUtil.fromLanguageId(languageId, false);
1891    
1892                                    String languageCode = languageId;
1893    
1894                                    int pos = languageId.indexOf(CharPool.UNDERLINE);
1895    
1896                                    if (pos > 0) {
1897                                            languageCode = languageId.substring(0, pos);
1898                                    }
1899    
1900                                    if (_languageCodeLocalesMap.containsKey(languageCode)) {
1901                                            _duplicateLanguageCodes.add(languageCode);
1902                                    }
1903                                    else {
1904                                            _languageCodeLocalesMap.put(languageCode, locale);
1905                                    }
1906    
1907                                    _languageIdLocalesMap.put(languageId, locale);
1908                            }
1909    
1910                            for (String languageId : PropsValues.LOCALES_BETA) {
1911                                    _localesBetaSet.add(
1912                                            LocaleUtil.fromLanguageId(languageId, false));
1913                            }
1914    
1915                            _availableLocales = Collections.unmodifiableSet(
1916                                    new HashSet<>(_languageIdLocalesMap.values()));
1917    
1918                            Set<Locale> supportedLocalesSet = new HashSet<>(
1919                                    _languageIdLocalesMap.values());
1920    
1921                            supportedLocalesSet.removeAll(_localesBetaSet);
1922    
1923                            _supportedLocalesSet = Collections.unmodifiableSet(
1924                                    supportedLocalesSet);
1925                    }
1926    
1927                    private final Set<Locale> _availableLocales;
1928                    private final Set<String> _duplicateLanguageCodes = new HashSet<>();
1929                    private final Map<String, Locale> _languageCodeLocalesMap =
1930                            new HashMap<>();
1931                    private final Map<String, Locale> _languageIdLocalesMap =
1932                            new HashMap<>();
1933                    private final Set<Locale> _localesBetaSet = new HashSet<>();
1934                    private final Set<Locale> _supportedLocalesSet;
1935    
1936            }
1937    
1938    }