[ 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: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]