//
you're reading...
Eureka!

Struts Message Sources from Non-Classpath Locations

For a lot of us using Struts1.x internationalization is as simple as having resource bundles in the classpath, and the simple struts-config.xml configuration:

<message-resources parameter="MyWebAppResources" null="false"/>

However, this approach has a simple limitation, that the resource bundles have to be in the classpath of the application. However, in one of the projects at work, one of the design restrictions was to not have properties in the WEB-INF/classes directory and also have the ability to add internationalized messages after the developers have done their job.

A little bit of hacking, and the solution was pretty easy. Four steps below did the job:

  • Create a Non-Classpath Message Resources class, extending struts PropertyMessageResources class:
    /*
     * Copyright (c) 2007, Bindul Bhowmik
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions are met:
     *
     * - Redistributions of source code must retain the above copyright notice, this
     *&nbsp;&nbsp; list of conditions and the following disclaimer.
     * - Redistributions in binary form must reproduce the above copyright notice,
     *&nbsp;&nbsp; this list of conditions and the following disclaimer in the documentation
     *&nbsp;&nbsp; and/or other materials provided with the distribution.
     * - Neither the name of Bindul Bhowmik nor the names of its contributors may be
     *&nbsp;&nbsp; used to endorse or promote products derived from this software without
     *&nbsp;&nbsp; specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     * POSSIBILITY OF SUCH DAMAGE.
     */
    package example.struts;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.util.Iterator;
    import java.util.Properties;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.apache.struts.util.MessageResourcesFactory;
    import org.apache.struts.util.PropertyMessageResources;
    
    /**
     * This is a custom implementation of the Struts
     * {@link PropertyMessageResources} to enable Application message files to be
     * located outside the classpath of the web application. This is required for
     * the custom layout and architecture of the application.<br />
     *
     * The factory which is used to instantiate new instances of this class is
     * {@link NonCpPropertyMessageResourcesFactory}.<br />
     *
     * The message files are resolved using a relative path from the web
     * application root.<br />
     *
     * To load properties using this way, the message configuration in the struts
     * configuration should be as follows:
     *
     * <pre>
     * &lt;message-resources key="resource" parameter="/path/from/web-app/root/resource"
     *&nbsp;&nbsp;&nbsp; factory="example.struts.NonCpPropertyMessageResourcesFactory" /&gt;
     * </pre>
     *
     * Where the parameter should point to the location of the property files. This
     * path is computed relative to the root of the web application.<br />
     *
     * The message resources can be Locale specific and should be of the format:
     * <ul>
     *&nbsp;&nbsp;&nbsp;&nbsp; <li><em>resource.properties</em> - The fallback resource properties</li>
     *&nbsp;&nbsp;&nbsp;&nbsp; <li><em>resource_en.properties</em> - The resource property for the english locale</li>
     *&nbsp;&nbsp;&nbsp;&nbsp; <li><em>resource_en_US.properties</em> - English (U.S.) locale resources</li>
     * </ul>
     *
     * @see MessageResources
     * @see NonCpPropertyMessageResourcesFactory
     * @author Bindul Bhowmik
     * @version $Revision: 1.1 $ $Date: 2007/06/29 23:28:04 $
     */
    public class NonCpPropertyMessageResources extends PropertyMessageResources {
    
     /**
     * Used for object serialization
     * @see java.io.Serializable
     */
     private static final long serialVersionUID = -1374082251950171470L;
    
     /**
     * Key to the system property which holds the directory location to the
     * web base.
     */
     private static final String WEB_BASE = "WEB_BASE";
    
     /**
     * The default extension of the message file.
     */
     private static final String MESSAGE_FILE_EXT = ".properties";
    
     /**
     * The logger for the class
     */
     private static Log log = LogFactory.getLog(NonCpPropertyMessageResources.class);
    
     /**
     * Constructs a new message resources instance.
     *
     * @param factory
     *&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; The factory which instantiated the instance
     * @param config
     *&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; The configuration string
     * @param returnNull
     *&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; If we should return <code>null</code> when property is not
     *&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; found.
     * @see PropertyMessageResources#PropertyMessageResources(MessageResourcesFactory,
     *&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String, boolean)
     */
     public NonCpPropertyMessageResources (MessageResourcesFactory factory, String config,
     boolean returnNull) {
     super(factory, config, returnNull);
     }
    
     /**
     * Creates a new message resources instance.
     *
     * @param factory
     *&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; The factory which instantiated the instance
     * @param config
     *&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; The configuration string
     * @see PropertyMessageResources#PropertyMessageResources(MessageResourcesFactory,
     *&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String)
     */
     public NonCpPropertyMessageResources (MessageResourcesFactory factory, String config) {
     super(factory, config);
     }
    
     /**
     * Loads the messages for a particular locale. This implementation loads the
     * files always relative to the WEB-BASE which is the root of the web
     * application.
     *
     * @see org.apache.struts.util.PropertyMessageResources#loadLocale(java.lang.String)
     */
     @SuppressWarnings ("unchecked")
     @Override
     protected synchronized void loadLocale (String localeKey) {
    
     if (log.isDebugEnabled()) {
     log.debug("Loading Locale: [" + localeKey + "]");
     }
    
     // Have we already attempted to load messages for this locale?
     if (locales.get(localeKey) != null) { return; }
    
     locales.put(localeKey, localeKey);
    
     // Set up to load the property resource for this locale key, if we can
     String name = config.replace('.', '/');
     if (localeKey.length() > 0) {
     name += "_" + localeKey;
     }
     name += MESSAGE_FILE_EXT;
     Properties props = new Properties();
    
     // Load the specified property resource
     if (log.isDebugEnabled()) {
     log.debug("Computed resource file: [" + name + "]");
     }
    
     // Get the web base
    
     String webBase = System.getProperty(WEB_BASE);
     // Compute the location of our file
     String fileLoc = webBase;
     if (!name.startsWith("/")) {
     fileLoc = fileLoc + "/";
     }
     fileLoc = fileLoc + name;
    
     if (log.isDebugEnabled()) {
     log.debug("Resolved resource file location: [" + fileLoc + "]. Attempting to load it");
     }
    
     File resourceFile = new File(fileLoc);
     if (resourceFile.exists()) {
     FileInputStream fis = null;
     try {
     fis = new FileInputStream(resourceFile);
     props.load(fis);
     } catch (FileNotFoundException e) {
     log.error("Configured message property file: [" + name
     + "] does not exist or could not be loaded.", e);
     } catch (IOException e) {
     log.error("Configured message property file: [" + name
     + "] does not exist or could not be loaded.", e);
     } finally {
     if (null != fis) {
     try {
     fis.close();
     } catch (IOException e) {
     log.warn("Error closing stream", e);
     }
     }
     }
     }
    
     if (log.isDebugEnabled()) {
     log.debug("Loading resource completed for locale: [" + localeKey + "]");
     }
    
     // Copy the corresponding values into our cache
     if (props.size() < 1) {
     // File does not exist or does not contain any properties.
     return;
     }
    
     synchronized (messages) {
     // Load the messages to our cache
     @SuppressWarnings ("rawtypes")
     Iterator names = props.keySet().iterator();
     while (names.hasNext()) {
     String key = (String) names.next();
     if (log.isDebugEnabled()) {
     log.debug("Saving message key: [" + messageKey(localeKey, key) + "]");
     }
     messages.put(messageKey(localeKey, key), props.getProperty(key));
     }
     }
     }
    }
    
  • Create a Factory class for the MessageResources created above:
    /*
     * Copyright (c) 2007, Bindul Bhowmik
     * All rights reserved.
     * 
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions are met:
     * 
     * - Redistributions of source code must retain the above copyright notice, this
     *   list of conditions and the following disclaimer.
     * - Redistributions in binary form must reproduce the above copyright notice,
     *   this list of conditions and the following disclaimer in the documentation
     *   and/or other materials provided with the distribution.
     * - Neither the name of Bindul Bhowmik nor the names of its contributors may be
     *   used to endorse or promote products derived from this software without
     *   specific prior written permission.
     * 
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     * POSSIBILITY OF SUCH DAMAGE.
     */
    package example.struts;
    
    import org.apache.struts.util.MessageResources;
    import org.apache.struts.util.PropertyMessageResourcesFactory;
    
    /**
     * The factory for creating new instances of
     * {@link NonCpPropertyMessageResources}. For a comprehensive discussion of the
     * message properties used in the application please see the class documentation
     * for {@link NonCpPropertyMessageResources}.
     * 
     * @see NonCpPropertyMessageResources
     * @see MessageResourcesFactory
     * @author Bindul Bhowmik
     * @version $Revision: 1.1 $ $Date: 2007/06/29 23:28:04 $
     */
    public class NonCpPropertyMessageResourcesFactory extends PropertyMessageResourcesFactory {
    
        /**
         * Used for object serialization.
         * 
         * @see java.io.Serializable
         */
        private static final long serialVersionUID = -363572862219957528L;
    
        /**
         * @see org.apache.struts.util.MessageResourcesFactory#createResources(java.lang.String)
         */
        @Override
        public MessageResources createResources (String config) {
            return new NonCpPropertyMessageResources(this, config, this.returnNull);
        }
    }
    
  • Create your resource bundle properties under the webapp
  • Configure message resoruces in struts
    <message-resources parameter="/WEB-INF/config/resources/example-messages" key="example-messages"  factory="example.struts.NonCpPropertyMessageResourcesFactory"/>
    

You can now use the message resources, just as you would use them with normal struts message resources.

I found this useful, and hopefully it will help someone else!

Discussion

No comments yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

About Random Musings

Random Musings is a collection of my personal thoughts, opinions, views and anything else that I find interesting. The views posted here are mine and may not necessarily reflect the views of MindTree Limited (my employer).
%d bloggers like this: