com.flexive.faces.beans.MessageBean.java Source code

Java tutorial

Introduction

Here is the source code for com.flexive.faces.beans.MessageBean.java

Source

/***************************************************************
 *  This file is part of the [fleXive](R) framework.
 *
 *  Copyright (c) 1999-2014
 *  UCS - unique computing solutions gmbh (http://www.ucs.at)
 *  All rights reserved
 *
 *  The [fleXive](R) project is free software; you can redistribute
 *  it and/or modify it under the terms of the GNU Lesser General Public
 *  License version 2.1 or higher as published by the Free Software Foundation.
 *
 *  The GNU Lesser General Public License can be found at
 *  http://www.gnu.org/licenses/lgpl.html.
 *  A copy is found in the textfile LGPL.txt and important notices to the
 *  license from the author are found in LICENSE.txt distributed with
 *  these libraries.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  For further information about UCS - unique computing solutions gmbh,
 *  please see the company website: http://www.ucs.at
 *
 *  For further information about [fleXive](R), please see the
 *  project website: http://www.flexive.org
 *
 *
 *  This copyright notice MUST APPEAR in all copies of the file!
 ***************************************************************/
package com.flexive.faces.beans;

import com.flexive.faces.FxJsfUtils;
import com.flexive.shared.FxContext;
import com.flexive.shared.FxFormatUtils;
import com.flexive.shared.FxSharedUtils;
import com.flexive.shared.value.FxValue;
import com.flexive.shared.value.renderer.FxValueRendererFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.net.URLClassLoader;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * <p>A generic localization beans for messages displayed in the UI. The MessageBean wraps
 * one or more {@link ResourceBundle ResourceBundles} that provide localized messages for
 * web applications or plugins. By providing a localized resource bundle with a fixed name
 * in your plugin/application JAR file, these messages will be automatically detected during
 * startup and can be accessed through the message beans.</p>
 * <p/>
 * <p>
 * Include the resource bundle with one of the following base names in the root directory
 * of a JAR file deployed with the application:
 * <p/>
 * <table>
 * <tr>
 * <th>{@link #BUNDLE_APPLICATIONS}</th>
 * <td>for web applications</td>
 * </tr>
 * <tr>
 * <th>{@link #BUNDLE_PLUGINS}</th>
 * <td>for plugins (or other JAR files providing localized messages)</td>
 * </tr>
 * </table>
 * <p/>
 * Currently both resource bundle types are treated equally, except that all application resource
 * bundles are queried before the first plugin resource bundle.
 * </p>
 * <p/>
 * <p><b>Usage:</b> fxMessageBean[key] to get the translation
 * of the given property name in the user's language.
 * Parameters in the localized message can be replaced too, by placing EL expressions inside the lookup string:
 * Using a message declaration of "my.message.key=1+1 is: {0}", the placeholder {0} will be replaced
 * using the following EL code:
 * <pre>
 * fxMessageBean['my.message.key,#{1+1}']
 * </pre>
 * </p>
 *
 * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 * @version $Rev$
 */
public class MessageBean extends HashMap {
    private static final long serialVersionUID = 2176514561683264331L;

    /**
     * Resource bundle name for plugin packages.
     */
    public static final String BUNDLE_PLUGINS = "PluginMessages";
    /**
     * Resource bundle name for web applications.
     */
    public static final String BUNDLE_APPLICATIONS = "ApplicationMessages";
    /**
     * Resource bundle name for exceptions.
     */
    public static final String BUNDLE_EXCEPTIONS = "FxExceptionMessages";

    private static final Log LOG = LogFactory.getLog(MessageBean.class);
    private final List<FxSharedUtils.BundleReference> resourceBundles = new CopyOnWriteArrayList<FxSharedUtils.BundleReference>();
    private final ConcurrentMap<String, ResourceBundle> cachedBundles = new ConcurrentHashMap<String, ResourceBundle>();
    private final ConcurrentMap<FxSharedUtils.MessageKey, String> cachedMessages = new ConcurrentHashMap<FxSharedUtils.MessageKey, String>();

    private volatile boolean initialized = false;
    private volatile boolean logMissingKeys = true;

    public MessageBean() {
        initialize();
    }

    /**
     * Return the managed instance of the message beans.
     *
     * @return the managed instance of the message beans.
     */
    public static MessageBean getInstance() {
        return (MessageBean) FxJsfUtils.getManagedBean("fxMessageBean");
    }

    /**
     * Return the localized message, replacing {0}...{n} with the given args.
     * < b/>
     * Parameters may be contained in the key and are comma separated. JSF EL expressions
     * are evaluated in the current faces context.<br/>
     * Examples: <br/>
     * key="xx.yy.myMessage,myParam1,myParam2" <br/>
     * key="xx.yy.myMessage,#{1+2},#{myBean.value},'some literal string value'"
     *
     * @param key the message key (optional with parameters)
     * @return the formatted message
     */
    @Override
    public Object get(Object key) {
        // The key may contain parameters, which are comma separated
        String sKey = String.valueOf(key);
        String sParams[] = null;
        try {
            String[] arr = FxSharedUtils.splitLiterals(sKey);
            sKey = arr[0];
            if (arr.length > 1) {
                sParams = new String[arr.length - 1];
                System.arraycopy(arr, 1, sParams, 0, sParams.length);
                for (int i = 1; i < arr.length; i++) {
                    // evaluate parameters
                    Object value;
                    try {
                        value = FxJsfUtils.evalObject(arr[i]);
                    } catch (Exception e) {
                        LOG.warn("Failed to evaluate parameter " + arr[i] + ": " + e.getMessage(), e);
                        value = "";
                    }
                    //noinspection unchecked
                    sParams[i - 1] = value != null
                            ? (value instanceof FxValue
                                    ? FxValueRendererFactory.getInstance().format((FxValue) value)
                                    : value.toString())
                            : null;
                }
            }
        } catch (Throwable t) {
            LOG.error("Failed to convert parameters (ignored): " + t.getMessage(), t);
        }

        return getMessage(sKey, (Object[]) sParams);
    }

    /**
     * Return the localized message, replacing {0}...{n} with the given args. FxString
     * objects will be translated in the user's locale automatically.
     *
     * @param key  message key
     * @param args optional arguments for
     * @return the formatted message
     */
    public String getMessage(String key, Object... args) {
        String result;
        try {
            result = getResource(key);
            if (args != null && args.length > 0) {
                result = FxFormatUtils.formatResource(result, FxContext.get().getLanguage().getId(), args);
            }
            return result;
        } catch (MissingResourceException e) {
            if (logMissingKeys) {
                LOG.warn("Unknown message key: " + key);
            }
            return "??" + key + "??";
        }
    }

    /**
     * Returns the resource translation in the current user's language.
     *
     * @param key resource key
     * @return the resource translation
     */
    public String getResource(String key) {
        final Locale locale = FxContext.get().getLocale();
        return getResource(key, locale);
    }

    /**
     * Returns the resource translation in the given locale.
     *
     * @param key       resource key
     * @param locale    the requested locale
     * @return the resource translation
     * @since 3.2.0
     */
    public String getResource(String key, Locale locale) {
        if (!initialized) {
            initialize();
        }
        final FxSharedUtils.MessageKey messageKey = new FxSharedUtils.MessageKey(locale, key);
        String cachedMessage = cachedMessages.get(messageKey);
        if (cachedMessage != null) {
            return cachedMessage;
        }
        for (FxSharedUtils.BundleReference bundleReference : resourceBundles) {
            try {
                final ResourceBundle bundle = getResources(bundleReference, locale);
                String message = bundle.getString(key);
                cachedMessage = cachedMessages.putIfAbsent(messageKey, message);
                return cachedMessage != null ? cachedMessage : message;
            } catch (MissingResourceException e) {
                // continue with next bundle
            }
        }
        if (!locale.equals(Locale.ENGLISH)) {
            //try to find the locale in english as last resort
            //this is a fix for using PropertyResourceBundles which can only handle one locale (have to use them thanks to JBoss 5...)
            for (FxSharedUtils.BundleReference bundleReference : resourceBundles) {
                try {
                    final ResourceBundle bundle = getResources(bundleReference, Locale.ENGLISH);
                    String message = bundle.getString(key);
                    cachedMessage = cachedMessages.putIfAbsent(messageKey, message);
                    return cachedMessage != null ? cachedMessage : message;
                } catch (MissingResourceException e) {
                    // continue with next bundle
                }
            }
        }
        throw new MissingResourceException("Resource not found", "MessageBean", key);
    }

    /**
     * Enable or disable the logging of keys without a translation (default: enabled).
     *
     * @param logMissingKeys    whether logging should be enabled
     * @since 3.2.1
     */
    public void setLogMissingKeys(boolean logMissingKeys) {
        this.logMissingKeys = logMissingKeys;
    }

    /**
     * Return the resource bundle in the given locale. Uses caching to speed up
     * lookups.
     *
     * @param bundleReference the bundle reference object
     * @param locale          the requested locale
     * @return the resource bundle in the requested locale
     */
    private ResourceBundle getResources(FxSharedUtils.BundleReference bundleReference, Locale locale) {
        final String key = bundleReference.getCacheKey(locale);
        ResourceBundle bundle = cachedBundles.get(key);
        if (bundle == null) {
            final ResourceBundle cachedBundle = cachedBundles.putIfAbsent(key,
                    bundle = bundleReference.getBundle(locale));
            if (cachedBundle != null) {
                return cachedBundle;
            }
        }
        return bundle;
    }

    /**
     * Initialize the application resource bundles. Scans the classpath for resource bundles
     * for a predefined set of names ({@link #BUNDLE_APPLICATIONS} and {@link #BUNDLE_PLUGINS}),
     * and then adds resource references that use {@link URLClassLoader URLClassLoaders} for loading
     * the associated resource bundles.
     */
    private void initialize() {
        if (initialized) {
            return;
        }
        try {
            resourceBundles.addAll(FxSharedUtils.addMessageResources(BUNDLE_APPLICATIONS));
            resourceBundles.addAll(FxSharedUtils.addMessageResources(BUNDLE_PLUGINS));
            resourceBundles.addAll(FxSharedUtils.addMessageResources(BUNDLE_EXCEPTIONS));
            resourceBundles.addAll(FxSharedUtils.addMessageResources(FxSharedUtils.SHARED_BUNDLE));
        } catch (IOException e) {
            LOG.error("Failed to initialize plugin message resources: " + e.getMessage(), e);
        } finally {
            initialized = true;
        }
    }

}