Home  Development Resources  EMail 

Multiple Struts Message Resource Files without Bundle Attribute

Abstract

Multiple message-resource files can be listed in Struts configuration by assigning a unique key attribute to each resource file or bundle. It is then necessary to specify bundle names when retrieving messages from resource files other than the default bundle. In my opinion this approach is somewhat clumsy and error prone, since an additional bundle attribute is necessary even if all message-resource keys are unique throughout an application. The solution set forth in this article allows to specify resource bundle names by means of a unique message key prefix, consistent with the way Java classes are distinguished by package name, eliminating the need for additional bundle attributes.

Struts with Multiple Message Resource Files

Multiple message-resource files can be listed in the Struts configuration "struts-config.xml" by assigning a unique key attribute to each message-resources tag:

    <message-resources 
         parameter="MessageResources" />

    <message-resources 
        key="module2" 
        parameter="com.complexproject.module2.MessageResources" />

To retrieve a message from module2 one would then specify a bundle attribute in conjunction with message-resource keys, the value for bundle matching the key from the message-resources tag.

    <bean:message bundle="module2" key="resource.from.module2" />

Loading message-resources from Java classes requires knowledge of the location of the related message-resource file in the application's class path:

    MessageResources messages = 
        MessageResources.getMessageResources(
            "com.complexproject.module2.MessageResources");
    String message = messages.getMessage("resource.from.module2");

In both cases described above two parameters are necessary to uniquely identify a message-resource. In my opinion this is not an ideal solution. Suppose an application framework exception handler needed to load message-resources from exceptions originating from arbitrary application modules to generate user friendly error messages, e.g. for display in a system error page. Exceptions from application modules with an own message-resource file would need to be instantiated with an extra filename parameter to pass user friendly internationalized error messages to the application framework. Clumsy and error prone, as the file or bundle parameter may easily be forgotten if a message-resource bundle is the default bundle within a test configuration but not in the deployment file.

Prefixing Message Keys with the Bundle Attribute

Passing bundle or message-resource filenames can be avoided by simple means, if a unique identifier for message-resource bundles is prepended to every message-resource key used within an application with multiple message-resource files. It is also possible to use a default message-resources bundle without prefixes so long as no message-resource key of the default bundle starts with an identifier that is used as a bundle prefix elsewhere.

    Messages from bundle1:
    bundle1.error.error1=Error one {0}
    bundle1.error.nopage=Error page not found

    Messages from bundle2:
    bundle2.hello=Welcome to bundle2
    bundle2.push=Please push the yellow button

With message-resource keys named as above, it is fairly easy to deduce bundle names from key strings. All that needs to be done now is to replace the standard Struts PropertyMessageResources with an implementation that takes advantage of the bundle prefixes. By using the factory attribute of the message-resources tag in the Struts configuration, this can be accomplished as follows:

    <message-resources 
         factory="info.crusius.struts.util.BundlePrefixMessageResourcesFactory"
         key="bundle1"
         parameter="bundle1.MessageResources" />

    <message-resources 
        factory="info.crusius.struts.util.BundlePrefixMessageResourcesFactory"
        key="bundle2" 
        parameter="bundle2.MessageResources" />

The BundlePrefixMessageResourcesFactory is a trivial implementation of the abstract Struts MessageResourcesFactory. It creates instances of BundlePrefixMessageResources that will be used by Struts to load message-resource strings.

/** Creates MessageResources that evaluate bundle prefixes.
 * 
 * @author Fritz Crusius
 *
 * BundlePrefixMessageResourcesFactory
 */
public class BundlePrefixMessageResourcesFactory extends
        MessageResourcesFactory {
    
    /**
     * 
     */
    public BundlePrefixMessageResourcesFactory() {
        super();
    }

    /* (non-Javadoc)
     * @see org.apache.struts.util.MessageResourcesFactory#createResources(java.lang.String)
     */
    public MessageResources createResources(String config) {
        return new BundlePrefixMessageResources(this, config, returnNull);
    }
    
}

The BundlePrefixMessageResources class below maintains a static map of all its instances related to message-resources other than the default bundle, with the bundle name as the key value. If the first part of a message key string, delimited by the first occurrence of a dot '.', matches a key of the static bundle map the message-resource is retrieved from the BundlePrefixMessageResources instance for that key.

Note that the BundlePrefixMessageResourcesFactory needs to be specified for all resource bundles an application is using. Moreover, if an application uses a default bundle, message-resources from the default bundle cannot be retrieved unless the first call to getMessage() is done on the default BundlePrefixMessageResources instance. In other words: do not specify a bundle if the message key belongs to the default bundle without bundle prefix.

/** MessageResources that deduce the bundle from the first section of the
 * message key, delimited by a '.'.
 * 
 * @author Fritz Crusius
 *
 * BundlePrefixMessageResources
 */
public class BundlePrefixMessageResources extends PropertyMessageResources {

    /** Prefixed message resource bundles
     * 
     */
    static HashMap bundleMap = new HashMap();

    /** Create message resources evaluating bundle prefixes
     * @param factory Concrete factory that created these message resources
     * @param config Configuration parameter(s) for the requested bundle
     * @param returnNull Return null for unknown key if true
     */
    public BundlePrefixMessageResources(MessageResourcesFactory factory,
            String config, boolean returnNull) {
        super(factory, config, returnNull);
        String key = factory.getConfig().getKey();
        if (key != null && key.length() > 0 && !key.endsWith(".MESSAGE")) {
            // Add any resource bundle except the default bundle
            // If no key is specified in the message-resources tag,
            // the Struts default is "org.apache.struts.action.MESSAGE"
            synchronized(bundleMap) {
                bundleMap.put(key, this);
            }
        }
    }

    /** Return message string from resource bundle related to the first key section
     * @param locale Locale for message
     * @param key Message key
     * @return Message
     */
    public String getMessage(Locale locale, String key) {
        int dotIdx = key.indexOf('.');
        if (dotIdx <= 0)
            // Key without prefix
            return super.getMessage(locale, key);
        String prefix = key.substring(0, dotIdx);
        BundlePrefixMessageResources bundleResources = null;
        synchronized(bundleMap) {
            if (bundleMap.containsKey(prefix))
                bundleResources = (BundlePrefixMessageResources)bundleMap.get(prefix);
        }
        if (bundleResources != null)
            return bundleResources.getPrefixedMessage(locale, key);
        return super.getMessage(locale, key);
    }
    
    /** Get message from prefixed resource bundle
     * 
     * @param locale Locale for Message
     * @param key Message key
     * @return Message
     */
    private final String getPrefixedMessage(Locale locale, String key) {
        return super.getMessage(locale, key);
    }
    
}

Conclusion

By extending the standard Struts PropertyMessageResources with a class that deduces message-resource bundles from prefixes of message keys, message-resources can be loaded from multiple message-resource files without passing an additional bundle attribute or message-resource filename to the Struts framework.

/* ---- struts-config.xml ---- */
    <message-resources 
         parameter="MessageResources" />

    <message-resources 
        key="module1" 
        parameter="com.complexproject.module1.MessageResources" />

    <message-resources 
        key="module2" 
        parameter="com.complexproject.module2.MessageResources" />

/* ---- JSP ---- */
    <bean:message key="resource.from.default.bundle" />
    <bean:message key="module1.resource.from.bundle1" />
    <bean:message key="module2.resource.from.bundle2" />

/* ---- Java ---- */
    MessageResources messages = 
        MessageResources.getMessageResources("MessageResources");
    String message = messages.getMessage("resource.from.default.bundle");
    message = messages.getMessage("module1.resource.from.bundle1");
    message = messages.getMessage("module2.resource.from.bundle2");

Reference Implementation

The sources for AppQuery and Struts extensions provide a reference implementation of the described mechanism for prefixed message-resource keys. The package info.crusius.struts.util contains the class BundlePrefixMessageResources and its factory.