DO NOT REPLY TO THIS EMAIL, BUT PLEASE POST YOUR BUG 
RELATED COMMENTS THROUGH THE WEB INTERFACE AVAILABLE AT
<http://nagoya.apache.org/bugzilla/show_bug.cgi?id=18289>.
ANY REPLY MADE TO THIS MESSAGE WILL NOT BE COLLECTED AND 
INSERTED IN THE BUG DATABASE.

http://nagoya.apache.org/bugzilla/show_bug.cgi?id=18289

Dynamic Message Resources

           Summary: Dynamic Message Resources
           Product: Struts
           Version: 1.1 RC1
          Platform: All
        OS/Version: Other
            Status: NEW
          Severity: Enhancement
          Priority: Other
         Component: Utilities
        AssignedTo: [EMAIL PROTECTED]
        ReportedBy: [EMAIL PROTECTED]


Attached code provides a subclass of MessageResources which dynamically reloads 
as changes are made.

==============================================================================

Consists of 5 files which follow:

DynamicResourceBundle.java - A resource bundle which refreshes itself as the 
underlying file(s) change

ResourceLoader.java - A subclass of ClassLoader which loads property bundles

ResourceLocation.java - A wrapper which encapsulates the location of a property 
bundle either in a property file or a jar file

BundleMessageResources.java - Subclass of MessageResources which uses an 
underlying DynamicResourceBundle to handle resource requests.

BundleMessageResourcesFactory.java

=============================================================================

package org.apache.struts.util;

import java.util.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * <p>Title: DynamicResourceBundle</p>
 * <p>Description: Takes care of monitoring underlying bundle locations for
 * changes and reloads (by creating a new ResourceLoader) if necessary</p>
 * <p>Copyright: None</p>
 * <p>Company: CoLinx</p>
 * @author Richard Lawson
 * @version 1.0
 * @todo we need to move this to a more general package
 */
public abstract class DynamicResourceBundle {
    /**
    * The <code>Log</code> instance for this class.
    */
    protected static final Log log = LogFactory.getLog
(DynamicResourceBundle.class);

    /**
    * Maximum length of one branch of the resource search path tree.
    * Used in getBundle.
    */
    protected static final int MAX_BUNDLES_SEARCHED = 3;

    /**
     * Cache of locales
     */
    protected static HashMap locales = new HashMap();

    /**
     * How often do we check the underlying resource for changes ?
     */
    protected static long checkInterval = 60000;

    /**
     *
     * @param interval ms between checks
     */
    public static synchronized void setCheckInterval(long interval) {
        checkInterval = interval;
    }

    /**
     *
     * @return ms between checks
     */
    public static long getCheckInterval() {
        return checkInterval;
    }

    /**
     *
     * @param baseName fully qualified base name of the resource
     * @param locale locale for messages
     * @return key
     */
    protected static String getKey(String baseName, Locale locale) {
        return baseName + ", " + locale.toString();
    }

    public static ResourceBundle getBundle(String baseName, Locale locale) {
        return getBundle(baseName, locale, null);
    }

    /**
     *
     * @param baseName
     * @param locale
     * @return
     */
    public static ResourceBundle getBundle(String baseName, Locale locale, 
ResourceLoader loader) {
        BundleMetaData meta = null;
        String key = getKey(baseName, locale);
        List locationList = null;
        synchronized (locale) {
            meta = (BundleMetaData) locales.get(key);

            if (meta == null) {
                if (loader == null) {
                    loader = new ResourceLoader();
                }

                locationList = calculateBundleLocations(baseName, locale, 
loader);
                meta = new BundleMetaData(locationList);
                locales.put(key, meta);
                log.info("Caching locations for bundle -> " + key + " | Cache 
size = " + locales.size());
            }

            if (bundleFilesHaveChanged(meta)) {
                log.warn("Underlying resources for bundle " + baseName + " , " 
+ locale + " have changed, reloading");
                meta.refreshLoader();
            }
        }

        // Resource bundle just returns a bundle from cache if already
        // loaded with this loader
        return ResourceBundle.getBundle(baseName, locale, meta.loader);
    }

    /**
    * Calculate the bundles along the search path from the base bundle to the
    * bundle specified by baseName and locale. Heavily based on the 
implementation
    * in ResourceBundle
    * @param baseName the base bundle name
    * @param locale the locale
    * the search path.
    * @return List of ResourceLocations for the bundle
    *
    */
    protected static List calculateBundleLocations(String baseName, Locale 
locale, ResourceLoader loader) {
        final ArrayList result = new ArrayList(MAX_BUNDLES_SEARCHED);
        final String language = locale.getLanguage();
        final int languageLength = language.length();
        final String country = locale.getCountry();
        final int countryLength = country.length();
        final String variant = locale.getVariant();
        final int variantLength = variant.length();
        ResourceLocation location = null;

        log.info("Calculating bundle resource locations for " + getKey
(baseName, locale));

        if ((languageLength + countryLength + variantLength) == 0) {
            //The locale is "", "", "".
            return result;
        }

        final StringBuffer temp = new StringBuffer(baseName.replace('.', '/'));
        location = loader.locateResource(temp.toString() + ".properties");

        if (location != null) {
            log.info("  --> Adding location " + location);
            result.add(location);
        }

        temp.append('_');
        temp.append(language);
        location = loader.locateResource(temp.toString() + ".properties");

        if (location != null) {
            log.info("  --> Adding location " + location);
            result.add(location);
        }

        if ((countryLength + variantLength) == 0) {
            return result;
        }

        temp.append('_');
        temp.append(country);
        location = loader.locateResource(temp.toString() + ".properties");

        if (location != null) {
            log.info("  --> Adding location " + location);
            result.add(location);
        }

        if (variantLength == 0) {
            return result;
        }

        temp.append('_');
        temp.append(variant);
        location = loader.locateResource(temp.toString() + ".properties");

        if (location != null) {
            log.info("  --> Adding location " + location);
            result.add(location);
        }

        return result;
    }

    /**
     *
     * @param meta Instance of BundleMetaData
     * @return true of any resources in the bundle have changed
     */
    protected static boolean bundleFilesHaveChanged(BundleMetaData meta) {
        // is it time to check even ?
        log.info("Checking locations for changes, time since last check = " + 
(System.currentTimeMillis() - meta.lastCheck));

        // if it is not time to check then just assume nothing has changed
        if (meta.isTimeToCheck() == false) {
            return false;
        }

        // check all the locations for changes
        Iterator locations = meta.locationList.iterator();

        while (locations.hasNext()) {
            ResourceLocation location = (ResourceLocation) locations.next();
            log.info("Checking location -> " + location);

            if (location.hasChanged()) {
                return true;
            }
        }

        // if we fall through then nothing has changed
        return false;
    }

    /**
     *
     * <p>Title: BundleMetaData</p>
     * <p>Description: Just a way to keep related info about a resource bundle
     * encapsulated in one place</p>
     * <p>Copyright: None</p>
     * <p>Company: CoLinx</p>
     * @author Richard Lawson
     * @version 1.0
     */
    protected static class BundleMetaData {
        /**
         * when was this bundle last checked for changes ?
         */
        long lastCheck = 0;

        /**
         * the loader used to load resource
         */
        ClassLoader loader = new ResourceLoader();

        /**
         * the list of locations that comprise this bundle
         */
        List locationList = null;

        /**
         *
         * @param locationList the list of resources that comprise this bundle
         */
        BundleMetaData(List locationList) {
            this.locationList = locationList;
        }

        /**
         * create a new loader for the resources so we can dynamically
         * load changes
         */
        void refreshLoader() {
            this.loader = new ResourceLoader();
        }

        /**
         *
         * @return flag indicating whether it is time to check the resource for
         * changes
         */
        boolean isTimeToCheck() {
            long currentTime = System.currentTimeMillis();
            long interval = currentTime - lastCheck;

            if (interval > checkInterval) {
                this.lastCheck = currentTime;

                return true;
            }

            return false;
        }
    }
}

================================================================================

package org.apache.struts.util;

import java.io.InputStream;

import java.net.MalformedURLException;
import java.net.URL;

import java.util.*;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * <p>Title: ResourceLoader </p>
 * <p>Description: Checks resources to see if timestamp has been modified,
 * if so then reloads the resource. Only works for property resources NOT
 * class based resources</p>
 * <p>Copyright: None</p>
 * <p>Company: CoLinx</p>
 * @author Richard Lawson
 * @version 1.0
 */
public class ResourceLoader extends ClassLoader {
    /**
    * The <code>Log</code> instance for this class.
    */
    protected static final Log log = LogFactory.getLog(ResourceLoader.class);

    /**
     * No args constructor
     */
    public ResourceLoader() {
    }

    /**
     *
     * @param name Fully qualified name of the resource to locate
     * @return InputStream to the resource
     */
    public InputStream getResourceAsStream(String name) {
        ResourceLocation resource = this.locateResource(name);

        if (resource != null) {
            log.info("Located resource returning stream -> " + name);

            // return the input stream if resource located
            return resource.getInputStream();
        } else {
            //log.info("Can't locate resource -> " + name);
            return null;
        }
    }

    /**
    * subclasses may want to override to provide different strategies
    * for locating resource. Here we use the ClassLoader and we always
    * check for new resources so we can add resources on the fly
    * @param name the name of the resource
    * @return URL of resource
    */
    public static ResourceLocation locateResource(String name) {
        ResourceLocation resource = null;

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader
();

        if (classLoader == null) {
            classLoader = ResourceLoader.class.getClassLoader();
        }

        URL url = classLoader.getResource(name);

        if (log.isTraceEnabled()) {
            log.debug("URL of resource: " + url);
        }

        // attempt to access the resource through the URL
        if (url != null) {
            try {
                resource = new ResourceLocation(url);
            } catch (MalformedURLException e) {
                log.error("Bad URL for resource -> " + resource);
            }
        }

        return resource;
    }
}

===============================================================================

package org.apache.struts.util;

import java.io.*;

import java.net.*;

import java.util.*;
import java.util.jar.*;

import org.apache.commons.lang.StringUtils;


/**
 * <p>Title: ResourceLocation</p>
 * <p>Description: Wrapper around resources located either in jars
 * or as files in the CLASSPATH.
 * Access to it needs to externally synchronized</p>
 * <p>Copyright: None</p>
 * <p>Company: CoLinx</p>
 * @author Richard Lawson
 * @version 1.0
 * @todo we need to move this to a more general utility class
 */
public class ResourceLocation {
    /**
     * Url of the resource
     */
    protected URL url = null;

    /**
     * Fully qualified filename of the underlying resource
     */
    protected String filename = null;

    /**
     * If this is a jar resource, this is the fully qualified name of the
     * resource in the jar
     */
    protected String entryname = null;

    /**
     * The last time this resource was modified
     */
    protected long lastmod = 0;

    /**
     *
     * @param url The URL which points to the resource
     * @throws MalformedURLException
     */
    public ResourceLocation(URL url) throws MalformedURLException {
        if (url == null) {
            throw new MalformedURLException();
        }


        // fill in state
        this.url = url;

        if (this.isJarResource(url)) {
            // extract the filename, entry name
            URL suburl = new URL(url.getPath());
            filename = suburl.getPath();


            // strip filename off
            entryname = StringUtils.prechomp(filename, "!");


            // chomp entry off
            filename = StringUtils.chomp(filename, "!");

            // strip off initial / if present on entry name
            // else JarFile can't locate the entry
            if (entryname.charAt(0) == '/') {
                entryname = StringUtils.substring(entryname, 1);
            }
        } else {
            filename = url.getPath();
        }
    }

    /**
     * Static convenience method for determining if a resource is a jar file
     * @param url The URL of the resource
     * @return true if URL points to a jar file
     */
    public static boolean isJarResource(URL url) {
        String proto = url.getProtocol();

        if (proto.equalsIgnoreCase("jar")) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Access the entry name
     * @return If resource is in a jar, this is the entry of the resource
     * within the jar. Else null.
     */
    public String getEntryName() {
        return this.entryname;
    }

    /**
     * Access the filename
     * @return The underlying filename of the resource
     */
    public String getFilename() {
        return this.filename;
    }

    /**
     * Access the URL
     * @return The URL of the resource
     */
    public URL getURL() {
        return this.url;
    }

    /**
     * Does this resource exist ?
     * Note that we check this each time so that resources can be dynamically
     * added, deleted
     * @return true if resource exists on file system
     */
    public boolean exists() {
        // construct a file object with path info
        File file = new File(filename);

        return file.exists();
    }

    /**
     * Has this resource changed since the last time this method was called ?
     * Synchronized cause we change the underlying object state
     * @return true if changed
     */
    public synchronized boolean hasChanged() {
        if (this.exists() == false) {
            return false;
        }

        long mod = this.lastModified();

        if (mod > this.lastmod) {
            this.lastmod = mod;

            return true;
        } else {
            return false;
        }
    }

    /**
     * When was this resource last modified ?
     * @return last modification time of underlying file
     * or 0 if the resource does not exist
     */
    public long lastModified() {
        // construct a file object with path info
        File file = new File(filename);

        if (file.exists()) {
            return file.lastModified();
        } else {
            return 0;
        }
    }

    /**
     *
     * @return String repr of object
     */
    public String toString() {
        StringBuffer repr = new StringBuffer();
        repr.append("Resource URL : " + this.url.toString() + "\n");
        repr.append("Filename : " + this.filename + "\n");

        if (this.entryname != null) {
            repr.append("Entryname : " + this.entryname + "\n");
        }

        repr.append("Exists ? : " + this.exists());

        return repr.toString();
    }

    /**
     *
     * @return Stream to the resource
     */
    public InputStream getInputStream() {
        InputStream is = null;

        // bail out if the resource is unavailable
        if (this.exists() == false) {
            return is;
        }

        try {
            // open stream to an entry in a jar file
            if (this.isJarResource(this.url)) {
                JarFile jarFile = new JarFile(filename);
                JarEntry entry = jarFile.getJarEntry(entryname);

                if (entry != null) {
                    is = jarFile.getInputStream(entry);
                }

                // open stream to a file
            } else {
                is = new FileInputStream(filename);
            }
        } catch (IOException e) {
            ;
        } finally {
            if (is == null) {
                // we always want to return something rather than a NP
                // in this case just return an empty stream
                byte[] buf = new byte[] {  };
                is = new ByteArrayInputStream(buf);
            }
        }

        return is;
    }
}

================================================================================

package org.apache.struts.util;

import java.io.InputStream;

import java.net.MalformedURLException;
import java.net.URL;

import java.util.*;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * <p>Title: BundleMessageResources</p>
 * <p>Description: Subclass of Struts MessageResources which uses a dynamically
 * reloadable resource bundle to locate messages</p>
 * <p>Copyright: None</p>
 * <p>Company: CoLinx</p>
 * @author Richard Lawson
 * @version 1.0
 */
public class BundleMessageResources extends MessageResources {
    /**
     *
     * @param factory The factory which created this resource
     * @param config String which holds the fully qualified base resource name
     */
    public BundleMessageResources(MessageResourcesFactory factory, String 
config) {
        super(factory, config);
    }

    /**
     *
     * @param factory The factory which created this resource
     * @param config String which holds the fully qualified base resource name
     * @param returnNull Flag to return null if can't locate message
     */
    public BundleMessageResources(MessageResourcesFactory factory, String 
config, boolean returnNull) {
        super(factory, config, returnNull);
    }

    /**
     *
     * @param locale Message locale
     * @param key Message key
     * @return The locale specific message
     */
    public String getMessage(Locale locale, String key) {
        ResourceBundle bundle = DynamicResourceBundle.getBundle(config, locale);

        if (bundle != null) {
            return bundle.getString(key);
        } else {
            log.warn("Can't locate bundle for locale,key -> " + locale + "," + 
key);

            if (returnNull) {
                return null;
            } else {
                return "??? " + key + " ???";
            }
        }
    }
}

===============================================================================

package org.apache.struts.util;

/**
 * <p>Title: BundleMessageResourcesFactory</p>
 * <p>Description: Called by the Struts framework to create 
BundleMessageResources</p>
 * <p>Copyright: None</p>
 * <p>Company: CoLinx</p>
 * @author Richard Lawson
 * @version 1.0
 */
public class BundleMessageResourcesFactory extends MessageResourcesFactory {
    /**
     *
     * @param config Fully qualified base name for the resource
     * @return BundleMessageResources
     */
    public MessageResources createResources(String config) {
        return new BundleMessageResources(this, config, this.returnNull);
    }
}

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to