webtier@glassfish.java.net

Re: [JSF] resource-bundle vs. message-bundle

From: Ed Burns <edward.burns_at_oracle.com>
Date: Tue, 8 Jun 2010 07:51:00 -0700

Because your question is of general interest, I'm answering it on the
webtier_at_glassfish forum.

>>>>> On Mon, 07 Jun 2010 19:26:29 +0530, Sanjeeb said:

SS> I am confused about message-bundle vs. resource-bundle. When to use one
SS> vs. another? I looked in the 2.0 spec and didn't find any meaningful
SS> distinction. After a lot of search in the net, I have concluded that:
SS> a) message-bundle allows one to specify only a single resource file in
SS> faces-config.xml and can be accessed using Application.getMessageBundle().
SS> b) resource-bundle allows one to specify multiple resource files and
SS> associate a request scoped variable for each one.

the schema for message-bundle says:

                            The base name of a resource bundle representing
                            the message resources for this application. See
                            the JavaDocs for the "java.util.ResourceBundle"
                            class for more information on the syntax of
                            resource bundle names.

the schema for resource-bundle says:

              The resource-bundle element inside the application element
              references a java.util.ResourceBundle instance by name
              using the var element. ResourceBundles referenced in this
              manner may be returned by a call to
              Application.getResourceBundle() passing the current
              FacesContext for this request and the value of the var
              element below.

I agree that usage information would be very helpful at both of these
points in the spec.

However, this text is taken from chapter 10 of my book
<http://bit.ly/edburnsjsf2>:

8<----------------------------

In addition to the standard Java platform techniques of using
ResourceBundles, Faces provides several ways to expose your
ResourceBundles to the Faces runtime via the Expression Language. A
simple Facelet example shows how easy it is:

<h:form>
<p><h:outputText value="#{bundle.greeting}" />
<h:outputText value="#{user.firstName}" />
</h:form>

If this looks like regular JSF code, that's because it is. Code in the
faces-config.xml file simply exposes a resource bundle as something that
can be accessed via the EL. This is one case where a faces-config.xml
file is still necessary. Create one and add the following code:

<application>

<resource-bundle>

<base-name>com.jsfcompref.trainer.Messages</base-name>
<var>bundle</var>

</resource-bundle>

</application>

Previous versions of JSF recommended using the f:loadBundle tag to associate a request-scoped variable called bundle with the ResourceBundle that uses the base name of com.jsfcompref.trainer.Messages. Since JSF 1.2, this is no longer recommended because it fails in the case of Ajax and partial page updates because the f:loadBundle tag may not always be executed in those cases.

In this example, the resource bundle is located under
com/jsfcompref/trainer in the file Resources.properties. The general
form for defining a ResourceBundle in a properties file is
<basename>_<language>_<country>_<variant>. The basename portion is a
fully qualified Java class name. As with actual .java files, the real
filename is only the last part of the fully qualified class name; the
previous parts of the fully qualified Java class name come from the
directory structure. The meaning of the language, country, and variant
extensions is explained later in the chapter, but for now, just know
that these extensions tell the Java runtime to what Locale object this
ResourceBundle applies. Also, note that the language, country, and
variant parameters are optional. Their absence means this ResourceBundle
is to be used as a fallback in case no better match for the requested
locale can be found.

8<----------------------------

SS> It looks to me that resource-bundle offers more flexibility than
SS> message-bundle. Then, when is message-bundle used?

You're already on to this with your below text. The message-bundle is
used to declare message ids for use in FacesMessages. For example, your
custom validator may want to convey a localized validation message. It
would do so by throwing a ValidatorException that is passed a
FacesMessage instance in the constructor.

SS> Further more, the
SS> javadocs of FacesMessage says that
SS> /A FacesMessage instance may be created based on a specific messageId./

SS> I don't see any constructor or factory method that accepts messageId.
SS> How is messageId used? Is it used by implementation? It seems like one
SS> can override spec defined messages by their own messages using
SS> message-bundle.If so, where are all the messageIds exposed?

Very good point. I argued with Craig about this in JSF 1.0.
Unfortunately, right now it's an implementation detail. I have attached
the source of the package private MessageFactory impl that we have
wherever we need it in mojarra.

There is an issue filed for this. It is targeted for 2.1

https://javaserverfaces.dev.java.net/issues/show_bug.cgi?id=721

I hope this helps. Furthermore, I hope this wrinkle does not increase
your skepticism of JSF by too much.

Sincerely,

Ed



/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2010 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package @package@;

import javax.el.ValueExpression;
import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.ApplicationFactory;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;

import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

/**
 *
 * <p>supported filters: <code>package</code> and
 * <code>protection</code>.</p>
 */

@protection@ class MessageFactory {

    private static final String MOJARRA_RESOURCE_BASENAME =
        "com.sun.faces.resources.Messages";

    private MessageFactory() {
    }

    /**
     * @see #getMessage(String, Object...)
     * @param FacesMessage.Serverity set a custom severity
     */
    @protection@ static FacesMessage getMessage(String messageId,
                                                FacesMessage.Severity severity,
                                                Object... params) {
        FacesMessage message = getMessage(messageId, params);
        message.setSeverity(severity);
        return message;
    }


    /**
     * @see #getMessage(Locale, String, Object...)
     * @param FacesMessage.Serverity set a custom severity
     */
    @protection@ static FacesMessage getMessage(Locale locale,
                                                String messageId,
                                                FacesMessage.Severity severity,
                                                Object... params) {
        FacesMessage message = getMessage(locale, messageId, params);
        message.setSeverity(severity);
        return message;
    }


    /**
     * @see #getMessage(FacesContext, String, Object...)
     * @param FacesMessage.Serverity set a custom severity
     */
    @protection@ static FacesMessage getMessage(FacesContext context,
                                                String messageId,
                                                FacesMessage.Severity severity,
                                                Object... params) {
        FacesMessage message = getMessage(context, messageId, params);
        message.setSeverity(severity);
        return message;
    }

   
    /**
     * <p>This version of getMessage() is used for localizing implementation
     * specific messages.</p>
     *
     * @param messageId - the key of the message in the resource bundle
     * @param params - substittion parameters
     *
     * @return a localized <code>FacesMessage</code> with the severity
     * of FacesMessage.SEVERITY_ERROR
     */
     @protection@ static FacesMessage getMessage(String messageId,
                                                 Object... params) {
        Locale locale = null;
        FacesContext context = FacesContext.getCurrentInstance();
        // context.getViewRoot() may not have been initialized at this point.
        if (context != null && context.getViewRoot() != null) {
            locale = context.getViewRoot().getLocale();
            if (locale == null) {
                locale = Locale.getDefault();
            }
        } else {
            locale = Locale.getDefault();
        }
        
        return getMessage(locale, messageId, params);
    }

     /**
      * <p>Creates and returns a FacesMessage for the specified Locale.</p>
      *
      * @param locale - the target <code>Locale</code>
      * @param messageId - the key of the message in the resource bundle
      * @param params - substittion parameters
      *
      * @return a localized <code>FacesMessage</code> with the severity
      * of FacesMessage.SEVERITY_ERROR
      */
     @protection@ static FacesMessage getMessage(Locale locale,
                                                 String messageId,
                                                 Object... params) {
        String summary = null;
        String detail = null;
        ResourceBundle bundle;
        String bundleName;

        // see if we have a user-provided bundle
        if (null != (bundleName = getApplication().getMessageBundle())) {
            if (null !=
                (bundle =
                    ResourceBundle.getBundle(bundleName, locale,
                      getCurrentLoader(bundleName)))) {
                // see if we have a hit
                try {
                    summary = bundle.getString(messageId);
                    detail = bundle.getString(messageId + "_detail");
                }
                catch (MissingResourceException e) {
                    // ignore
                }
            }
        }
    
        // we couldn't find a summary in the user-provided bundle
        if (null == summary) {
            // see if we have a summary in the app provided bundle
            bundle = ResourceBundle.getBundle(FacesMessage.FACES_MESSAGES,
                                              locale,
                                              getCurrentLoader(bundleName));
            if (null == bundle) {
                throw new NullPointerException();
            }
            // see if we have a hit
            try {
                summary = bundle.getString(messageId);
                detail = bundle.getString(messageId + "_detail");
            } catch (MissingResourceException e) {
                // ignore
            }
        }

        // no hit found in the standard javax.faces.Messages bundle.
        // check the Mojarra resources
        if (summary == null) {
            // see if we have a summary in the app provided bundle
            bundle = ResourceBundle.getBundle(MOJARRA_RESOURCE_BASENAME,
                                              locale,
                                              getCurrentLoader(bundleName));
            if (null == bundle) {
                throw new NullPointerException();
            }
            // see if we have a hit
            try {
                summary = bundle.getString(messageId);
            } catch (MissingResourceException e) {
                return null;
            }
        }

        // At this point, we have a summary and a bundle.
        FacesMessage ret = new BindingFacesMessage(locale, summary, detail, params);
        ret.setSeverity(FacesMessage.SEVERITY_ERROR);
        return (ret);
    }


    /**
     * <p>Creates and returns a FacesMessage for the specified Locale.</p>
     *
     * @param context - the <code>FacesContext</code> for the current request
     * @param messageId - the key of the message in the resource bundle
     * @param params - substittion parameters
     *
     * @return a localized <code>FacesMessage</code> with the severity
     * of FacesMessage.SEVERITY_ERROR
     */
    @protection@ static FacesMessage getMessage(FacesContext context,
                                                String messageId,
                                                Object... params) {
                                                
        if (context == null || messageId == null ) {
            throw new NullPointerException(" context "
                + context
                + " messageId "
                + messageId);
        }
        Locale locale;
        // viewRoot may not have been initialized at this point.
        if (context.getViewRoot() != null) {
            locale = context.getViewRoot().getLocale();
        } else {
            locale = Locale.getDefault();
        }
        
        if (null == locale) {
            throw new NullPointerException(" locale is null ");
        }
        
        FacesMessage message = getMessage(locale, messageId, params);
        if (message != null) {
            return message;
        }
        locale = Locale.getDefault();
        return (getMessage(locale, messageId, params));
    }
                       

    /**
     * <p>Returns the <code>label</code> property from the specified
     * component.</p>
     *
     * @param context - the <code>FacesContext</code> for the current request
     * @param component - the component of interest
     *
     * @return the label, if any, of the component
     */
    @protection@ static Object getLabel(FacesContext context,
                                        UIComponent component) {
                                        
        Object o = component.getAttributes().get("label");
        if (o == null || (o instanceof String && ((String) o).length() == 0)) {
            o = component.getValueExpression("label");
        }
        // Use the "clientId" if there was no label specified.
        if (o == null) {
            o = component.getClientId(context);
        }
        return o;
    }

    protected static Application getApplication() {
        FacesContext context = FacesContext.getCurrentInstance();
        if (context != null) {
            return (FacesContext.getCurrentInstance().getApplication());
        }
        ApplicationFactory afactory = (ApplicationFactory)
            FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY);
        return (afactory.getApplication());
    }

    protected static ClassLoader getCurrentLoader(Object fallbackClass) {
        ClassLoader loader =
            Thread.currentThread().getContextClassLoader();
        if (loader == null) {
            loader = fallbackClass.getClass().getClassLoader();
        }
        return loader;
    }

    /**
     * This class overrides FacesMessage to provide the evaluation
     * of binding expressions in addition to Strings.
     * It is often the case, that a binding expression may reference
     * a localized property value that would be used as a
     * substitution parameter in the message. For example:
     * <code>#{bundle.userLabel}</code>
     * "bundle" may not be available until the page is rendered.
     * The "late" binding evaluation in <code>getSummary</code> and
     * <code>getDetail</code> allow the expression to be evaluated
     * when that property is available.
     */
    static class BindingFacesMessage extends FacesMessage {
        BindingFacesMessage(
            Locale locale,
            String messageFormat,
            String detailMessageFormat,
            // array of parameters, both Strings and ValueBindings
            Object[] parameters) {

            super(messageFormat, detailMessageFormat);
            this.locale = locale;
            this.parameters = parameters;
            if (parameters != null) {
                resolvedParameters = new Object[parameters.length];
            }
        }

        public String getSummary() {
            String pattern = super.getSummary();
            resolveBindings();
            return getFormattedString(pattern, resolvedParameters);
        }

        public String getDetail() {
            String pattern = super.getDetail();
            resolveBindings();
            return getFormattedString(pattern, resolvedParameters);
        }

        private void resolveBindings() {
            FacesContext context = null;
            if (parameters != null) {
                for (int i = 0; i < parameters.length; i++) {
                    Object o = parameters[i];
                    if (o instanceof ValueBinding) {
                        if (context == null) {
                            context = FacesContext.getCurrentInstance();
                        }
                        o = ((ValueBinding) o).getValue(context);
                    }
                    if (o instanceof ValueExpression) {
                        if (context == null) {
                            context = FacesContext.getCurrentInstance();
                        }
                        o = ((ValueExpression) o).getValue(context.getELContext());
                    }
                    // to avoid 'null' appearing in message
                    if (o == null) {
                        o = "";
                    }
                    resolvedParameters[i] = o;
                }
            }
        }

        private String getFormattedString(String msgtext, Object[] params) {
            String localizedStr = null;
                                                                                
            if (params == null || msgtext == null ) {
                return msgtext;
            }
            StringBuffer b = new StringBuffer(100);
            MessageFormat mf = new MessageFormat(msgtext);
            if (locale != null) {
                mf.setLocale(locale);
                b.append(mf.format(params));
                localizedStr = b.toString();
            }
            return localizedStr;
        }

        private Locale locale;
        private Object[] parameters;
        private Object[] resolvedParameters;
    }
    
} // end of class MessageFactory









-- 
| edburns_at_oracle.com | office: +1 650 633 7407
| homepage:          | http://ridingthecrest.com/
| 23 Work Days Til JSF 2.1 Milestone 1