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