struts-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Don Brown (JIRA)" <j...@apache.org>
Subject [jira] Reopened: (STR-1339) Dynamic Message Resources
Date Tue, 25 Apr 2006 19:22:46 GMT
     [ http://issues.apache.org/struts/browse/STR-1339?page=all ]
     
Don Brown reopened STR-1339:
----------------------------

    Assign To:     (was: Struts Developer Mailing List)

> Dynamic Message Resources
> -------------------------
>
>          Key: STR-1339
>          URL: http://issues.apache.org/struts/browse/STR-1339
>      Project: Struts Action 1
>         Type: Improvement

>   Components: Action
>     Versions: 1.1 RC1
>  Environment: Operating System: other
> Platform: All
>     Reporter: Richard Lawson
>     Priority: Minor

>
> 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);
>     }
> }

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
   http://issues.apache.org/struts/secure/Administrators.jspa
-
For more information on JIRA, see:
   http://www.atlassian.com/software/jira


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@struts.apache.org
For additional commands, e-mail: dev-help@struts.apache.org


Mime
View raw message