cocoon-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From vgritse...@apache.org
Subject svn commit: r312968 - in /cocoon/branches/BRANCH_2_1_X: ./ src/java/org/apache/cocoon/components/source/impl/validity/ src/java/org/apache/cocoon/i18n/ src/java/org/apache/cocoon/transformation/ src/webapp/WEB-INF/ src/webapp/samples/i18n/translations/
Date Tue, 11 Oct 2005 22:29:07 GMT
Author: vgritsenko
Date: Tue Oct 11 15:28:46 2005
New Revision: 312968

URL: http://svn.apache.org/viewcvs?rev=312968&view=rev
Log:
    <action dev="VG" type="update">
      I18n: Refactored XMLResourceBundle to use transient store instead of
      private cache. Added reload check interval parameter. Support dynamic
      additions and removals of resource bundles, without need to restart
      Cocoon.
    </action>


Added:
    cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/source/impl/validity/
    cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/source/impl/validity/DelayedValidity.java   (with props)
Modified:
    cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/Bundle.java
    cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/BundleFactory.java
    cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/I18nUtils.java
    cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/XMLResourceBundle.java
    cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/XMLResourceBundleFactory.java
    cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/transformation/I18nTransformer.java
    cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf
    cocoon/branches/BRANCH_2_1_X/src/webapp/samples/i18n/translations/menu.xml
    cocoon/branches/BRANCH_2_1_X/status.xml

Added: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/source/impl/validity/DelayedValidity.java
URL: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/source/impl/validity/DelayedValidity.java?rev=312968&view=auto
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/source/impl/validity/DelayedValidity.java (added)
+++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/source/impl/validity/DelayedValidity.java Tue Oct 11 15:28:46 2005
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cocoon.components.source.impl.validity;
+
+import org.apache.excalibur.source.SourceValidity;
+
+/**
+ * Delays validity check for a specified interval.
+ *
+ * <p>
+ * This is wrapper validity which can be used to reduce count of
+ * filesystem (or network) accesses just to check the source
+ * validity.
+ *
+ * @since 2.1.8
+ * @version $Id$
+ */
+public class DelayedValidity implements SourceValidity {
+
+    private long delay;
+    private long expires;
+
+    private SourceValidity delegate;
+
+
+    public DelayedValidity(long delay, SourceValidity validity) {
+        this.delay = delay;
+        this.expires = System.currentTimeMillis() + delay;
+        this.delegate = validity;
+    }
+
+    public int isValid() {
+        final long currentTime = System.currentTimeMillis();
+        if (currentTime <= this.expires) {
+            // The delay has not passed yet - assuming source is valid.
+            return SourceValidity.VALID;
+        }
+
+        // The delay has passed, prepare for the next interval.
+        this.expires = currentTime + this.delay;
+
+        return this.delegate.isValid();
+    }
+
+    public int isValid(SourceValidity newValidity) {
+        // Always delegate
+        return this.delegate.isValid(newValidity);
+    }
+}

Propchange: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/source/impl/validity/DelayedValidity.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/source/impl/validity/DelayedValidity.java
------------------------------------------------------------------------------
    svn:keywords = Id

Modified: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/Bundle.java
URL: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/Bundle.java?rev=312968&r1=312967&r2=312968&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/Bundle.java (original)
+++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/Bundle.java Tue Oct 11 15:28:46 2005
@@ -1,12 +1,12 @@
 /*
- * Copyright 1999-2004 The Apache Software Foundation.
- * 
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *      http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -20,11 +20,11 @@
 import org.apache.avalon.framework.component.Component;
 
 /**
- * Resource bundle component interface. 
+ * Resource bundle component interface.
  * Provide the minimal number of methods to be used for i18n.
  *
  * @author <a href="mailto:kpiroumian@apache.org">Konstantin Piroumian</a>
- * @version CVS $Id: Bundle.java,v 1.4 2004/03/05 13:02:56 bdelacretaz Exp $
+ * @version $Id$
  */
 public interface Bundle extends Component {
 
@@ -33,7 +33,7 @@
     /**
      * Get string value by key.
      *
-     * @param key 
+     * @param key
      * @return Resource as string.
      * @exception MissingResourceException if resource was not found
      */

Modified: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/BundleFactory.java
URL: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/BundleFactory.java?rev=312968&r1=312967&r2=312968&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/BundleFactory.java (original)
+++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/BundleFactory.java Tue Oct 11 15:28:46 2005
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2004 The Apache Software Foundation.
+ * Copyright 1999-2005 The Apache Software Foundation.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,18 +15,18 @@
  */
 package org.apache.cocoon.i18n;
 
-import java.util.Locale;
-
 import org.apache.avalon.framework.component.Component;
 import org.apache.avalon.framework.component.ComponentException;
 
+import java.util.Locale;
+
 /**
  * Bundle Factory implementations are responsible for loading and providing
  * particular types of resource bundles, implementors of Bundle interface.
  *
  * @author <a href="mailto:kpiroumian@apache.org">Konstantin Piroumian</a>
  * @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
- * @version CVS $Id$
+ * @version $Id$
  */
 public interface BundleFactory extends Component {
 
@@ -36,11 +36,31 @@
     String ROLE = BundleFactory.class.getName();
 
     /**
-     * Constants for configuration keys
+     * Constants for bundle factory configuration keys
      */
     static class ConfigurationKeys {
-        public static final String CACHE_AT_STARTUP = "cache-at-startup";
+        /**
+         * Configuration element specifying default location of the
+         * resource bundles.
+         *
+         * @see BundleFactory#select(String, String)
+         * @see BundleFactory#select(String, java.util.Locale)
+         */
         public static final String ROOT_DIRECTORY = "catalogue-location";
+
+        /**
+         * Configuration element specifying role of the Store instance to use
+         * for storing cached bundles
+         * @since 2.1.8
+         */
+        public static final String STORE_ROLE = "store-role";
+
+        /**
+         * Configuration element specifying delay (in ms) between
+         * reload checks.
+         * @since 2.1.8
+         */
+        public static final String RELOAD_INTERVAL = "reload-interval";
     }
 
     /**
@@ -101,5 +121,10 @@
      */
     Bundle select(String bundleName, Locale locale) throws ComponentException;
 
+    /**
+     * Releases a bundle back to the bundle factory when it's not needed
+     * anymore.
+     * @param bundle the bundle
+     */
     void release(Bundle bundle);
 }

Modified: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/I18nUtils.java
URL: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/I18nUtils.java?rev=312968&r1=312967&r2=312968&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/I18nUtils.java (original)
+++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/I18nUtils.java Tue Oct 11 15:28:46 2005
@@ -33,7 +33,7 @@
  *
  * @author <a href="mailto:kpiroumian@apache.org">Konstantin Piroumian</a>
  * @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
- * @version CVS $Id$
+ * @version $Id$
  */
 public class I18nUtils {
 

Modified: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/XMLResourceBundle.java
URL: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/XMLResourceBundle.java?rev=312968&r1=312967&r2=312968&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/XMLResourceBundle.java (original)
+++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/XMLResourceBundle.java Tue Oct 11 15:28:46 2005
@@ -15,34 +15,34 @@
  */
 package org.apache.cocoon.i18n;
 
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
 import org.apache.avalon.framework.logger.AbstractLogEnabled;
-import org.apache.avalon.framework.service.ServiceException;
-import org.apache.avalon.framework.service.ServiceManager;
-import org.apache.avalon.framework.service.Serviceable;
-import org.apache.cocoon.ProcessingException;
-import org.apache.cocoon.components.source.SourceUtil;
-import org.apache.cocoon.xml.ParamSaxBuffer;
 import org.apache.excalibur.source.Source;
 import org.apache.excalibur.source.SourceNotFoundException;
 import org.apache.excalibur.source.SourceResolver;
 import org.apache.excalibur.source.SourceValidity;
+import org.apache.excalibur.source.impl.validity.ExpiresValidity;
+
+import org.apache.cocoon.ResourceNotFoundException;
+import org.apache.cocoon.components.source.SourceUtil;
+import org.apache.cocoon.components.source.impl.validity.DelayedValidity;
+import org.apache.cocoon.xml.ParamSaxBuffer;
+
 import org.xml.sax.Attributes;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.Locator;
 import org.xml.sax.SAXException;
 
+import java.net.MalformedURLException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
 /**
  * Implementation of <code>Bundle</code> interface for XML resources. Represents a
  * single XML message bundle.
  *
+ * <p>
  * XML format for this resource bundle implementation is the following:
  * <pre>
  * &lt;catalogue xml:lang="en"&gt;
@@ -52,15 +52,19 @@
  * &lt;/catalogue&gt;
  * </pre>
  *
+ * <p>
  * Value can be any well formed XML snippet and it will be cached by the key specified
  * in the attribute <code>key</code>. Objects returned by this {@link Bundle} implementation
  * are instances of the {@link ParamSaxBuffer} class.
  *
+ * <p>
+ * If value for a key is not present in this bundle, parent bundle will be queried.
+ *
  * @author <a href="mailto:dev@cocoon.apache.org">Apache Cocoon Team</a>
  * @version $Id$
  */
 public class XMLResourceBundle extends AbstractLogEnabled
-                               implements Bundle, Serviceable {
+                               implements Bundle {
 
     /**
      * Namespace for the Bundle markup
@@ -105,12 +109,8 @@
     /**
      * Objects stored in the bundle
      */
-    protected HashMap values;
+    protected Map values;
 
-    /**
-     * Service Manager
-     */
-    protected ServiceManager manager;
 
     /**
      * Processes XML bundle file and creates map of values
@@ -159,6 +159,7 @@
                     this.namespace = ns;
                     this.state++;
                     break;
+
                 case 1:
                     // <i18n:message>
                     if (!EL_MESSAGE.equals(localName)) {
@@ -178,9 +179,11 @@
                     this.values.put(key, this.buffer);
                     this.state++;
                     break;
+
                 case 2:
                     this.buffer.startElement(ns, localName, qName, atts);
                     break;
+
                 default:
                     throw new SAXException("Internal error: Invalid state");
             }
@@ -190,10 +193,12 @@
             switch (this.state) {
                 case 0:
                     break;
+
                 case 1:
                     // </i18n:catalogue>
                     this.state--;
                     break;
+
                 case 2:
                     if (this.namespace.equals(ns) && EL_MESSAGE.equals(localName)) {
                         // </i18n:message>
@@ -203,6 +208,7 @@
                         this.buffer.endElement(ns, localName, qName);
                     }
                     break;
+
                 default:
                     throw new SAXException("Internal error: Invalid state");
             }
@@ -233,89 +239,101 @@
         }
     }
 
-    /**
-     * Compose this instance
-     *
-     * @param manager The <code>ServiceManager</code> instance
-     * @throws ServiceException
-     */
-    public void service(ServiceManager manager) throws ServiceException {
-        this.manager = manager;
-    }
-
-    /**
-     * Implements Disposable interface for this class.
-     */
-    public void dispose() {
-        this.manager = null;
-    }
 
     /**
-     * Initalize the bundle
-     *
+     * Construct a bundle.
      * @param sourceURI source URI of the XML bundle
      * @param locale locale
      * @param parent parent bundle of this bundle
-     *
-     * @throws IOException if an IO error occurs while reading the file
-     * @throws ProcessingException if an error occurs while loading the bundle
-     * @throws SAXException if an error occurs while loading the bundle
      */
-    public void init(String sourceURI, Locale locale, Bundle parent)
-    throws IOException, ProcessingException, SAXException {
+    public XMLResourceBundle(String sourceURI, Locale locale, Bundle parent) {
         this.sourceURI = sourceURI;
         this.locale = locale;
         this.parent = parent;
-        this.values = new HashMap();
-        load();
+        this.values = Collections.EMPTY_MAP;
     }
 
     /**
-     * Load the XML bundle, based on the source URI.
-     *
-     * @exception IOException if an IO error occurs while reading the file
-     * @exception ProcessingException if no parser is configured
-     * @exception SAXException if an error occurs while parsing the file
-     */
-    protected void load() throws IOException, ProcessingException, SAXException {
-        Source source = null;
-        SourceResolver resolver = null;
+     * (Re)Loads the XML bundle if necessary, based on the source URI.
+     * @return true if reloaded successfully
+     */
+    protected boolean reload(SourceResolver resolver, long interval) {
+        Source newSource = null;
+        Map newValues;
+
         try {
             int valid = this.validity == null ? SourceValidity.INVALID : this.validity.isValid();
             if (valid != SourceValidity.VALID) {
                 // Saved validity is not valid, get new source and validity
-                resolver = (SourceResolver) this.manager.lookup(SourceResolver.ROLE);
-                source = resolver.resolveURI(this.sourceURI);
-                SourceValidity sourceValidity = source.getValidity();
-                if (valid == SourceValidity.INVALID || this.validity.isValid(sourceValidity) != SourceValidity.VALID) {
-                    HashMap values = new HashMap();
-                    SourceUtil.toSAX(source, new SAXContentHandler(values));
-                    this.validity = sourceValidity;
-                    this.values = values;
-                    if (getLogger().isDebugEnabled()) {
-                        getLogger().debug("Loaded XML bundle: " + this.sourceURI + ", locale: " + this.locale);
+                newSource = resolver.resolveURI(this.sourceURI);
+                SourceValidity newValidity = newSource.getValidity();
+
+                if (valid == SourceValidity.INVALID || this.validity.isValid(newValidity) != SourceValidity.VALID) {
+                    newValues = new HashMap();
+                    SourceUtil.toSAX(newSource, new SAXContentHandler(newValues));
+                    synchronized (this) {
+                        // Update source validity and values
+                        if (interval > 0 && newValidity != null) {
+                            this.validity = new DelayedValidity(interval, newValidity);
+                        } else {
+                            this.validity = newValidity;
+                        }
+                        this.values = newValues;
                     }
                 }
             }
-        } catch (ServiceException e) {
-            throw new ProcessingException("Can't lookup source resolver", e);
+
+            // Success
+            return true;
+
         } catch (MalformedURLException e) {
-            throw new SourceNotFoundException("Invalid resource URL: " + this.sourceURI, e);
+            getLogger().error("Bundle <" + this.sourceURI + "> not loaded: Invalid URI", e);
+            newValues = Collections.EMPTY_MAP;
+
+        } catch (ResourceNotFoundException e) {
+            if (getLogger().isDebugEnabled()) {
+                getLogger().info("Bundle <" + sourceURI + "> not loaded: Source URI not found", e);
+            } else if (getLogger().isInfoEnabled()) {
+                getLogger().info("Bundle <" + sourceURI + "> not loaded: Source URI not found");
+            }
+            newValues = Collections.EMPTY_MAP;
+
+        } catch (SourceNotFoundException e) {
+            if (getLogger().isDebugEnabled()) {
+                getLogger().info("Bundle <" + sourceURI + "> not loaded: Source URI not found", e);
+            } else if (getLogger().isInfoEnabled()) {
+                getLogger().info("Bundle <" + sourceURI + "> not loaded: Source URI not found");
+            }
+            newValues = Collections.EMPTY_MAP;
+
+        } catch (SAXException e) {
+            getLogger().error("Bundle <" + sourceURI + "> not loaded: Invalid XML", e);
+            // Keep existing loaded values
+            newValues = this.values;
+
+        } catch (Exception e) {
+            getLogger().error("Bundle <" + sourceURI + "> not loaded: Exception", e);
+            // Keep existing loaded values
+            newValues = this.values;
+
         } finally {
-            if (source != null) {
-                resolver.release(source);
+            if (newSource != null) {
+                resolver.release(newSource);
             }
-            this.manager.release(resolver);
         }
-    }
 
-    /**
-     * Gets the validity of the bundle.
-     *
-     * @return the validity
-     */
-    public SourceValidity getValidity() {
-        return this.validity;
+        synchronized (this) {
+            // Use expires validity to delay next reloading.
+            if (interval > 0) {
+                this.validity = new ExpiresValidity(interval);
+            } else {
+                this.validity = null;
+            }
+            this.values = newValues;
+        }
+
+        // Failure
+        return false;
     }
 
     /**
@@ -328,10 +346,28 @@
     }
 
     /**
-     * Get Object value by key.
+     * Gets the source URI of the bundle.
+     *
+     * @return the source URI
+     */
+    public String getSourceURI() {
+        return this.sourceURI;
+    }
+
+    /**
+     * Gets the validity of the bundle.
+     *
+     * @return the validity
+     */
+    public SourceValidity getValidity() {
+        return this.validity;
+    }
+
+    /**
+     * Get an instance of the {@link ParamSaxBuffer} associated with the key.
      *
      * @param key the key
-     * @return the value
+     * @return the value, or null if no value associated with the key.
      */
     public Object getObject(String key) {
         if (key == null) {
@@ -339,18 +375,22 @@
         }
 
         Object value = this.values.get(key);
-        if (value == null && this.parent != null) {
-            value = this.parent.getObject(key);
+        if (value != null) {
+            return value;
+        }
+
+        if (this.parent != null) {
+            return this.parent.getObject(key);
         }
 
-        return value;
+        return null;
     }
 
     /**
-     * Get String value by key.
+     * Get a string representation of the value object by key.
      *
      * @param key the key
-     * @return the value
+     * @return the string value, or null if no value associated with the key.
      */
     public String getString(String key) {
         if (key == null) {
@@ -362,31 +402,10 @@
             return value.toString();
         }
 
-        if(this.parent != null) {
+        if (this.parent != null) {
             return this.parent.getString(key);
         }
 
         return null;
-    }
-
-    /**
-     * Return a set of keys.
-     *
-     * @return the enumeration of keys
-     */
-    public Set keySet() {
-        return Collections.unmodifiableSet(this.values.keySet());
-    }
-
-    /**
-     * Reload this bundle if URI's timestamp is newer than ours.
-     */
-    public void update() {
-        try {
-            load();
-        } catch (Exception e) {
-            getLogger().info("Resource update failed. " + this.sourceURI + ", locale: " + this.locale
-                             + " Exception: " + e);
-        }
     }
 }

Modified: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/XMLResourceBundleFactory.java
URL: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/XMLResourceBundleFactory.java?rev=312968&r1=312967&r2=312968&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/XMLResourceBundleFactory.java (original)
+++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/i18n/XMLResourceBundleFactory.java Tue Oct 11 15:28:46 2005
@@ -15,30 +15,24 @@
  */
 package org.apache.cocoon.i18n;
 
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Locale;
-import java.util.Map;
-
 import org.apache.avalon.framework.CascadingRuntimeException;
 import org.apache.avalon.framework.activity.Disposable;
 import org.apache.avalon.framework.component.ComponentException;
 import org.apache.avalon.framework.configuration.Configurable;
 import org.apache.avalon.framework.configuration.Configuration;
 import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
 import org.apache.avalon.framework.logger.LogEnabled;
-import org.apache.avalon.framework.logger.Logger;
 import org.apache.avalon.framework.service.ServiceException;
 import org.apache.avalon.framework.service.ServiceManager;
 import org.apache.avalon.framework.service.Serviceable;
 import org.apache.avalon.framework.thread.ThreadSafe;
-import org.apache.cocoon.ResourceNotFoundException;
 import org.apache.excalibur.source.Source;
-import org.apache.excalibur.source.SourceNotFoundException;
 import org.apache.excalibur.source.SourceResolver;
-import org.xml.sax.SAXParseException;
+import org.apache.excalibur.store.Store;
+
+import java.io.IOException;
+import java.util.Locale;
 
 /**
  * This is the XMLResourceBundleFactory, the method for getting and creating
@@ -48,35 +42,25 @@
  * @author <a href="mailto:neeme@one.lv">Neeme Praks</a>
  * @author <a href="mailto:oleg@one.lv">Oleg Podolsky</a>
  * @author <a href="mailto:kpiroumian@apache.org">Konstantin Piroumian</a>
+ * @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
  * @version $Id$
  */
-public class XMLResourceBundleFactory
-       implements BundleFactory, Serviceable, Configurable, Disposable, ThreadSafe, LogEnabled {
-
-    /**
-     * Cache of the bundles
-     */
-    protected final Map cache = Collections.synchronizedMap(new HashMap());
-
-    /**
-     * Cache for the bundles that were not found
-     */
-    protected final Map cacheNotFound = new HashMap();
-
-    /**
-     * Should we load bundles to cache on startup or not?
-     */
-    protected boolean cacheAtStartup;
+public class XMLResourceBundleFactory extends AbstractLogEnabled
+                                      implements BundleFactory, Serviceable, Configurable,
+                                                 Disposable, ThreadSafe, LogEnabled {
 
     /**
      * Root directory to all bundle names
      */
-    protected String directory;
+    private String directory;
 
     /**
-     * The logger
+     * Reload check interval in milliseconds.
+     * Defaults to 60000 (1 minute), use <code>-1</code> to
+     * disable reloads and <code>0</code> to check for modifications
+     * on each catalogue request.
      */
-    private Logger logger;
+    private long interval;
 
     /**
      * Service Manager
@@ -88,40 +72,19 @@
      */
     protected SourceResolver resolver;
 
-
     /**
-     * Default constructor
+     * Store of the loaded bundles
      */
-    public XMLResourceBundleFactory() {
-    }
+    protected Store cache;
 
-    /**
-     * @see org.apache.avalon.framework.logger.LogEnabled#enableLogging(org.apache.avalon.framework.logger.Logger)
-     */
-    public void enableLogging(Logger logger) {
-        this.logger = logger;
-    }
 
-    public Logger getLogger() {
-        return this.logger;
-    }
+    //
+    // Lifecycle
+    //
 
     public void service(ServiceManager manager) throws ServiceException {
         this.manager = manager;
-        this.resolver = (SourceResolver)this.manager.lookup(SourceResolver.ROLE);
-    }
-
-    public void dispose() {
-        Iterator i = this.cache.values().iterator();
-        while (i.hasNext()) {
-            Object bundle = i.next();
-            if (bundle instanceof Disposable) {
-                ((Disposable)bundle).dispose();
-            }
-            i.remove();
-        }
-        this.manager.release(this.resolver);
-        this.manager = null;
+        this.resolver = (SourceResolver) this.manager.lookup(SourceResolver.ROLE);
     }
 
     /**
@@ -130,48 +93,54 @@
      * @param configuration the configuration
      */
     public void configure(Configuration configuration) throws ConfigurationException {
-        this.cacheAtStartup = configuration.getChild(ConfigurationKeys.CACHE_AT_STARTUP).getValueAsBoolean(false);
+        this.directory = configuration.getChild(ConfigurationKeys.ROOT_DIRECTORY).getValue("");
 
+        String cacheRole = configuration.getChild(ConfigurationKeys.STORE_ROLE).getValue(Store.TRANSIENT_STORE);
         try {
-            this.directory = configuration.getChild(ConfigurationKeys.ROOT_DIRECTORY, true).getValue();
-        } catch (ConfigurationException e) {
-            if (getLogger().isWarnEnabled()) {
-                getLogger().warn("Root directory not provided in configuration, using default (root).");
-            }
-            this.directory = "";
+            this.cache = (Store) this.manager.lookup(cacheRole);
+        } catch (ServiceException e) {
+            throw new ConfigurationException("Unable to lookup store '" + cacheRole + "'");
         }
 
+        this.interval = configuration.getChild(ConfigurationKeys.RELOAD_INTERVAL).getValueAsLong(60000L);
+
         if (getLogger().isDebugEnabled()) {
-            getLogger().debug("Configured with: cacheAtStartup = " +
-                              this.cacheAtStartup + ", directory = '" + this.directory + "'");
+            getLogger().debug("Bundle directory '" + this.directory + "'");
+            getLogger().debug("Store role '" + cacheRole + "'");
         }
     }
 
     /**
-     * Returns the root directory to all bundles.
-     *
-     * @return the directory path
+     * Disposes this component.
      */
-    protected String getDirectory() {
-        return this.directory;
+    public void dispose() {
+        this.manager.release(this.resolver);
+        this.manager.release(this.cache);
+        this.resolver = null;
+        this.cache = null;
+        this.manager = null;
     }
 
+    //
+    // BundleFactory Interface
+    //
+
     /**
-     * Should we load bundles to cache on startup or not?
+     * Returns the root directory to all bundles.
      *
-     * @return true if pre-loading all resources; false otherwise
+     * @return the directory path
      */
-    protected boolean cacheAtStartup() {
-        return this.cacheAtStartup;
+    protected String getDirectory() {
+        return this.directory;
     }
 
     /**
      * Select a bundle based on the bundle name and the locale name.
      *
-     * @param name    bundle name
-     * @param locale  locale name
-     * @return        the bundle
-     * @exception     ComponentException if a bundle is not found
+     * @param name        bundle name
+     * @param locale      locale name
+     * @return            the bundle
+     * @exception         ComponentException if a bundle is not found
      */
     public Bundle select(String name, String locale) throws ComponentException {
         return select(getDirectory(), name, locale);
@@ -180,10 +149,10 @@
     /**
      * Select a bundle based on the bundle name and the locale.
      *
-     * @param name    bundle name
-     * @param locale  locale
-     * @return        the bundle
-     * @exception     ComponentException if a bundle is not found
+     * @param name        bundle name
+     * @param locale      locale
+     * @return            the bundle
+     * @exception         ComponentException if a bundle is not found
      */
     public Bundle select(String name, Locale locale) throws ComponentException {
         return select(getDirectory(), name, locale);
@@ -193,11 +162,11 @@
      * Select a bundle based on the catalogue base location, bundle name,
      * and the locale name.
      *
-     * @param directory    catalogue base location (URI)
-     * @param name    bundle name
+     * @param directory   catalogue base location (URI)
+     * @param name        bundle name
      * @param localeName  locale name
-     * @return        the bundle
-     * @exception     ComponentException if a bundle is not found
+     * @return            the bundle
+     * @exception         ComponentException if a bundle is not found
      */
     public Bundle select(String directory, String name, String localeName)
     throws ComponentException {
@@ -208,17 +177,15 @@
      * Select a bundle based on the catalogue base location, bundle name,
      * and the locale.
      *
-     * @param directory    catalogue base location (URI)
-     * @param name    bundle name
-     * @param locale  locale
-     * @return        the bundle
-     * @exception     ComponentException if a bundle is not found
+     * @param directory   catalogue base location (URI)
+     * @param name        bundle name
+     * @param locale      locale
+     * @return            the bundle
+     * @exception         ComponentException if a bundle is not found
      */
     public Bundle select(String directory, String name, Locale locale)
     throws ComponentException {
-        String[] directories = new String[1];
-        directories[0] = directory;
-        return select(directories, name, locale);
+        return select(new String[] { directory }, name, locale);
     }
 
     /**
@@ -226,10 +193,10 @@
      * and the locale.
      *
      * @param directories catalogue base location (URI)
-     * @param name    bundle name
-     * @param locale  locale
-     * @return        the bundle
-     * @exception     ComponentException if a bundle is not found
+     * @param name        bundle name
+     * @param locale      locale
+     * @return            the bundle
+     * @exception         ComponentException if a bundle is not found
      */
     public Bundle select(String[] directories, String name, Locale locale)
     throws ComponentException {
@@ -240,6 +207,14 @@
         return bundle;
     }
 
+    public void release(Bundle bundle) {
+        // Do nothing
+    }
+
+    //
+    // Implementation
+    //
+
     /**
      * Select a bundle based on bundle name and locale.
      *
@@ -249,7 +224,8 @@
      * @return                  the bundle
      */
     private XMLResourceBundle _select(String[] directories, int index, String name,
-                                      Locale locale) {
+                                      Locale locale)
+    throws ComponentException {
         if (getLogger().isDebugEnabled()) {
             getLogger().debug("Selecting from: " + name + ", locale: " + locale +
                               ", directory: " + directories[index]);
@@ -265,25 +241,21 @@
                     boolean localeAvailable = (locale != null && !locale.getLanguage().equals(""));
                     index++;
 
-                    XMLResourceBundle parentBundle = null;
+                    // Find parent bundle first
+                    XMLResourceBundle parent = null;
                     if (localeAvailable && index == directories.length) {
                         // all directories have been searched with this locale,
                         // now start again with the first directory and the parent locale
-                        parentBundle = _select(directories, 0, name, getParentLocale(locale));
+                        parent = _select(directories, 0, name, getParentLocale(locale));
                     } else if (index < directories.length) {
                         // there are directories left to search for with this locale
-                        parentBundle = _select(directories, index, name, locale);
-                    }
-
-                    if (!isNotFoundBundle(cacheKey)) {
-                        final String fileName = getFileName(directories[index - 1], name, locale);
-                        bundle = _loadBundle(name, fileName, locale, parentBundle);
-                        updateCache(cacheKey, bundle);
+                        parent = _select(directories, index, name, locale);
                     }
 
-                    if (bundle == null) {
-                        return parentBundle;
-                    }
+                    // Create this bundle (if source exists) and pass parent to it.
+                    final String sourceURI = getSourceURI(directories[index - 1], name, locale);
+                    bundle = _create(sourceURI, locale, parent);
+                    updateCache(cacheKey, bundle);
                 }
             }
         }
@@ -291,44 +263,27 @@
     }
 
     /**
-     * Construct a bundle based on bundle name, file name and locale.
+     * Constructs new bundle.
      *
-     * @param name              bundle name
-     * @param fileName          full path to source XML file
-     * @param locale            locale
-     * @return                  the bundle, null if loading failed
-     */
-    private XMLResourceBundle _loadBundle(String name, String fileName, Locale locale,
-                                          XMLResourceBundle parentBundle) {
+     * <p>
+     * If there is a problem loading the bundle, created bundle will be empty.
+     *
+     * @param sourceURI   source URI of the XML resource bundle
+     * @param locale      locale of the bundle
+     * @param parent      parent bundle, if any
+     * @return            the bundle
+     */
+    private XMLResourceBundle _create(String sourceURI,
+                                      Locale locale,
+                                      XMLResourceBundle parent) {
         if (getLogger().isDebugEnabled()) {
-            getLogger().debug("Loading bundle: " + name + ", locale: " + locale +
-                              ", uri: " + fileName);
+            getLogger().debug("Creating bundle <" + sourceURI + ">");
         }
 
-        XMLResourceBundle bundle = null;
-        try {
-            bundle = new XMLResourceBundle();
-            bundle.enableLogging(this.logger);
-            bundle.service(this.manager);
-            bundle.init(fileName, locale, parentBundle);
-            return bundle;
-        } catch (ResourceNotFoundException e) {
-            getLogger().info("Resource not found: " + name + ", locale: " + locale +
-                             ", bundleName: " + fileName + ". Exception: " + e);
-        } catch (SourceNotFoundException e) {
-            getLogger().info("Resource not found: " + name + ", locale: " + locale +
-                             ", bundleName: " + fileName + ". Exception: " + e);
-        } catch (SAXParseException e) {
-            getLogger().error("Incorrect resource format", e);
-        } catch (Exception e) {
-            getLogger().error("Resource loading failed", e);
-        }
-
-        return null;
-    }
-
-    public void release(Bundle bundle) {
-        // Do nothing
+        XMLResourceBundle bundle = new XMLResourceBundle(sourceURI, locale, parent);
+        bundle.enableLogging(getLogger());
+        bundle.reload(this.resolver, this.interval);
+        return bundle;
     }
 
     /**
@@ -336,8 +291,8 @@
      * E.g. the parent of new Locale("en","us","mac") would be
      * new Locale("en", "us", "").
      *
-     * @param locale            the locale
-     * @return                  the parent locale
+     * @param locale      the locale
+     * @return            the parent locale
      */
     protected Locale getParentLocale(Locale locale) {
         Locale newloc;
@@ -353,38 +308,45 @@
         return newloc;
     }
 
+    /**
+     * Creates a cache key for the bundle.
+     * @return the cache key
+     */
     protected String getCacheKey(String[] directories, int index, String name, Locale locale) {
-        StringBuffer cacheKey = new StringBuffer();
+        StringBuffer cacheKey = new StringBuffer("XRB");
         for (; index < directories.length; index++) {
-            cacheKey.append(getFileName(directories[index], name, locale));
             cacheKey.append(":");
+            cacheKey.append(getSourceURI(directories[index], name, locale));
         }
         return cacheKey.toString();
     }
 
     /**
-     * Maps a bundle name and locale to a full path in the filesystem.
+     * Maps a bundle name and locale to a bundle source URI.
      * If you need a different mapping, then just override this method.
      *
-     * @param locale            the locale
-     * @return                  the parent locale
-     */
-    protected String getFileName(String base, String name, Locale locale) {
+     * @param base    the base URI for the catalogues
+     * @param name    the name of the catalogue
+     * @param locale  the locale of the bundle
+     * @return        the source URI for the bundle
+     */
+    protected String getSourceURI(String base, String name, Locale locale) {
+        // If base is null default to the current location
+        if (base == null) {
+            base = "";
+        }
+
         StringBuffer sb = new StringBuffer();
-        if (base == null || base.length() == 0) {
-            // FIXME (SW): can this happen?
-        } else {
-            try {
-                Source src = this.resolver.resolveURI(base);
-                String uri = src.getURI();
-                sb.append(uri);
-                if (!uri.endsWith("/")) {
-                    sb.append('/');
-                }
-                this.resolver.release(src);
-            } catch(IOException ioe) {
-                throw new CascadingRuntimeException("Cannot resolve " + base, ioe);
+        try {
+            Source src = this.resolver.resolveURI(base);
+            String uri = src.getURI();
+            sb.append(uri);
+            if (!uri.endsWith("/")) {
+                sb.append('/');
             }
+            this.resolver.release(src);
+        } catch (IOException e) {
+            throw new CascadingRuntimeException("Cannot resolve " + base, e);
         }
 
         sb.append(name);
@@ -407,64 +369,42 @@
 
         String result = sb.toString();
         if (getLogger().isDebugEnabled()) {
-            getLogger().debug("Resolved bundle name: " + name +
+            getLogger().debug("Resolved name: " + name +
                               ", locale: " + locale + " --> " + result);
         }
         return result;
     }
 
     /**
-     * Selects a bundle from the cache.
+     * Selects a bundle from the cache, and reloads it if needed.
      *
-     * @param cacheKey          caching key of the bundle
-     * @return                  the cached bundle; null, if not found
+     * @param cacheKey    caching key of the bundle
+     * @return            the cached bundle; null, if not found
      */
     protected XMLResourceBundle selectCached(String cacheKey) {
         XMLResourceBundle bundle = (XMLResourceBundle) this.cache.get(cacheKey);
-        if (bundle != null) {
-            bundle.update();
-        }
 
-        if (getLogger().isDebugEnabled()) {
-            getLogger().debug((bundle == null? "NOT ":"") + "In cache: " + cacheKey);
+        if (bundle != null && this.interval != -1) {
+            // Reload this bundle and all parent bundles, as necessary
+            for (XMLResourceBundle b = bundle; b != null; b = (XMLResourceBundle) b.parent) {
+                b.reload(this.resolver, this.interval);
+            }
         }
-        return bundle;
-    }
-
-    /**
-     * Checks if the bundle is in the &quot;not-found&quot; cache.
-     *
-     * @param cacheKey          caching key of the bundle
-     * @return                  true, if the bundle wasn't found already before;
-     *                          otherwise, false.
-     */
-    protected boolean isNotFoundBundle(String cacheKey) {
-        Object result = this.cacheNotFound.get(cacheKey);
 
-        if (getLogger().isDebugEnabled()) {
-            getLogger().debug((result == null? "NOT ":"") + "In not_found_cache: " + cacheKey);
-        }
-        return result != null;
+        return bundle;
     }
 
     /**
-     * Stores bundle in the cache (or in the &quot;not-found&quot; cache,
-     * if bundle is null)
+     * Stores bundle in the cache.
      *
-     * @param cacheKey          caching key of the bundle
-     * @param bundle            bundle to be placed in the cache
+     * @param cacheKey    caching key of the bundle
+     * @param bundle      bundle to be placed in the cache
      */
     protected void updateCache(String cacheKey, XMLResourceBundle bundle) {
-        if (bundle == null) {
-            if (getLogger().isDebugEnabled()) {
-                getLogger().debug("Updating not_found_cache: " + cacheKey);
-            }
-            this.cacheNotFound.put(cacheKey, cacheKey);
-        } else {
-            if (getLogger().isDebugEnabled()) {
-                getLogger().debug("Updating cache: " + cacheKey);
-            }
-            this.cache.put(cacheKey, bundle);
+        try {
+            this.cache.store(cacheKey, bundle);
+        } catch (IOException e) {
+            getLogger().error("Bundle <" + bundle.getSourceURI() + ">: unable to store.", e);
         }
     }
 }

Modified: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/transformation/I18nTransformer.java
URL: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/transformation/I18nTransformer.java?rev=312968&r1=312967&r2=312968&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/transformation/I18nTransformer.java (original)
+++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/transformation/I18nTransformer.java Tue Oct 11 15:28:46 2005
@@ -818,11 +818,6 @@
      */
     private String defaultUntranslated;
 
-    /**
-     * Cache at startup configuration parameter value
-     */
-    private boolean cacheAtStartup;
-
     //
     // Local configuration variables
     //
@@ -989,7 +984,7 @@
             String id = catalogueConfs[i].getAttribute("id");
             String name = catalogueConfs[i].getAttribute("name");
 
-            String[] locations = null;
+            String[] locations;
             String location = catalogueConfs[i].getAttribute("location", null);
             Configuration[] locationConf =
                 catalogueConfs[i].getChildren("location");
@@ -1044,13 +1039,8 @@
 
         // Obtain default text to use for untranslated messages
         defaultUntranslated = conf.getChild(I18N_UNTRANSLATED).getValue(null);
-
-        // Obtain config option, whether to cache messages at startup time
-        cacheAtStartup = conf.getChild(I18N_CACHE_STARTUP).getValueAsBoolean(false);
-
         if (getLogger().isDebugEnabled()) {
             getLogger().debug("Default untranslated text is '" + defaultUntranslated + "'");
-            getLogger().debug((cacheAtStartup ? "will" : "won't") + " cache messages during startup");
         }
     }
 
@@ -1847,8 +1837,8 @@
         }
 
         // Formatters
-        SimpleDateFormat to_fmt = null;
-        SimpleDateFormat from_fmt = null;
+        SimpleDateFormat to_fmt;
+        SimpleDateFormat from_fmt;
 
         // Date formatting styles
         int srcStyle = DateFormat.DEFAULT;
@@ -1925,7 +1915,7 @@
         }
 
         // parsed date object
-        Date dateValue = null;
+        Date dateValue;
 
         // pattern overwrites locale format
         if (realSrcPattern) {
@@ -2004,7 +1994,7 @@
         }
 
         // parsed number
-        Number numberValue = null;
+        Number numberValue;
 
         // locale, may be switched locale
         Locale loc = getLocale(params, I18N_LOCALE_ATTRIBUTE);
@@ -2031,7 +2021,7 @@
         }
 
         // to format
-        DecimalFormat to_fmt = null;
+        DecimalFormat to_fmt;
         char dec = from_fmt.getDecimalFormatSymbols().getDecimalSeparator();
         int decAt = 0;
         boolean appendDec = false;

Modified: cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf
URL: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf?rev=312968&r1=312967&r2=312968&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf (original)
+++ cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf Tue Oct 11 15:28:46 2005
@@ -560,14 +560,22 @@
       | I18n Bundle Factory
       |
       | BundleFactory loads Bundles with i18n resources for the given locale.
-      | Bundles are loaded from the 'catalogue_location'. Bundle base name is
-      | 'catalogue_name' value.
-      | If 'cache-at-startup' is true then BundleFactory preloads bundles.
+      | Default location for bundles specified with the 'catalogue-location'.
       +-->
   <i18n-bundles logger="core.i18n">
-    <catalogue-name>messages</catalogue-name>
+    <!--+
+        | Role of the store component to be used for caching loaded bundles.
+        +-->
+    <store-role>org.apache.excalibur.store.Store/TransientStore</store-role>
+    <!--+
+        | Reload check delay. Default 60000 (1 minute), 0 means no delay
+        | (check always), -1 means no reload.
+        +-->
+    <reload-interval>60000</reload-interval>
+    <!--+
+        | Location of the default message catalogue. Optional.
+        +-->
     <catalogue-location>context://samples/i18n/translations</catalogue-location>
-    <cache-at-startup>true</cache-at-startup>
   </i18n-bundles>
 
 <!-- ====================== System Components =========================== -->

Modified: cocoon/branches/BRANCH_2_1_X/src/webapp/samples/i18n/translations/menu.xml
URL: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/webapp/samples/i18n/translations/menu.xml?rev=312968&r1=312967&r2=312968&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/webapp/samples/i18n/translations/menu.xml (original)
+++ cocoon/branches/BRANCH_2_1_X/src/webapp/samples/i18n/translations/menu.xml Tue Oct 11 15:28:46 2005
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
-  Copyright 1999-2004 The Apache Software Foundation
+  Copyright 1999-2005 The Apache Software Foundation
 
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
@@ -18,21 +18,27 @@
 <!--+
     | Default (English) message catalogue file for menu in Cocoon 2 i18n sample
     |
-    | CVS $Id: menu.xml,v 1.3 2004/03/06 02:25:36 antonio Exp $
+    | $Id$
     +-->
-
 <catalogue xml:lang="en">
     <message key="Apache Cocoon i18n Samples">Apache Cocoon i18n Samples</message>
+
     <message key="Samples">Samples</message>
     <message key="Introduction">Introduction</message>
     <message key="Static (XML)">Static (XML)</message>
     <message key="Dynamic (XSP)">Dynamic (XSP)</message>
     <message key="Sitemap source">Sitemap source</message>
+    <message key="Multi Catalogues">Multi Catalogues</message>
+    <message key="Tiered Catalogues">Tiered Catalogues</message>
+    <message key="Back">Back</message>
+
     <message key="Locales">Locales</message>
+
     <message key="Documentation">Documentation</message>
     <message key="i18n transformer docs"><![CDATA[<I18ntransformer> docs]]></message>
     <message key="i18n transformer Javadoc"><![CDATA[<I18ntransformer> Javadoc]]></message>
     <message key="LocaleAction Javadoc"><![CDATA[<LocaleAction> Javadoc]]></message>
+
     <message key="Credits">Credits</message>
     <message key="Konstantin Piroumian">Konstantin Piroumian</message>
     <message key="Many others...">Many others...</message>

Modified: cocoon/branches/BRANCH_2_1_X/status.xml
URL: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/status.xml?rev=312968&r1=312967&r2=312968&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/status.xml (original)
+++ cocoon/branches/BRANCH_2_1_X/status.xml Tue Oct 11 15:28:46 2005
@@ -197,6 +197,12 @@
 
   <changes>
   <release version="@version@" date="@date@">
+    <action dev="VG" type="update">
+      I18n: Refactored XMLResourceBundle to use transient store instead of
+      private cache. Added reload check interval parameter. Support dynamic
+      additions and removals of resource bundles, without need to restart
+      Cocoon.
+    </action>
     <action dev="CZ" type="update">
       JavaDocs and documentation are no longer copied to the webapp.
     </action>



Mime
View raw message