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