Index: server/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/camel/CamelCompositeReloadableProcessor.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- server/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/camel/CamelCompositeReloadableProcessor.java (revision ) +++ server/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/camel/CamelCompositeReloadableProcessor.java (revision ) @@ -0,0 +1,66 @@ +package org.apache.james.mailetcontainer.impl.camel; + +import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; +import org.apache.commons.configuration.HierarchicalConfiguration; +import org.apache.james.mailetcontainer.api.MailProcessor; +import org.apache.james.mailetcontainer.api.MailetLoader; +import org.apache.james.mailetcontainer.api.MatcherLoader; +import org.apache.james.mailetcontainer.lib.AbstractReloadableProcessor; +import org.apache.mailet.MailetContext; + +import javax.annotation.Resource; + +/** + * Reloadable {@link MailProcessor} delegates {@link #service(org.apache.mailet.Mail)} to {@link CamelCompositeProcessor}. + */ +public class CamelCompositeReloadableProcessor extends AbstractReloadableProcessor implements CamelContextAware { + private CamelContext camelContext; + private MailetContext mailetContext; + private MatcherLoader matcherLoader; + private MailetLoader mailetLoader; + + public void setCamelContext(CamelContext camelContext) { + this.camelContext = camelContext; + } + + public CamelContext getCamelContext() { + return camelContext; + } + + protected void loadDelegate(HierarchicalConfiguration config) throws Exception { + CamelCompositeProcessor processor = new CamelCompositeProcessor(); + try { + processor.setLog(getLogger()); + processor.setMailetLoader(mailetLoader); + processor.setMatcherLoader(matcherLoader); + processor.setMailetContext(mailetContext); + processor.setCamelContext(camelContext); + processor.configure(config); + processor.init(); + setDelegate(processor); + } catch (Exception e) { + processor.dispose(); + throw e; + } + } + + protected void destroyDelegate() { + ((CamelCompositeProcessor) getDelegate()).dispose(); + } + + @Resource(name = "mailetcontext") + public void setMailetContext(MailetContext mailetContext) { + this.mailetContext = mailetContext; + } + + @Resource(name = "mailetloader") + public void setMailetLoader(MailetLoader mailetLoader) { + this.mailetLoader = mailetLoader; + } + + @Resource(name = "matcherloader") + public void setMatcherLoader(MatcherLoader matcherLoader) { + this.matcherLoader = matcherLoader; + } +} Index: server/lifecycle-spring/src/main/java/org/apache/james/container/spring/lifecycle/ConfigurationProvider.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- server/lifecycle-spring/src/main/java/org/apache/james/container/spring/lifecycle/ConfigurationProvider.java (revision 1340913) +++ server/lifecycle-spring/src/main/java/org/apache/james/container/spring/lifecycle/ConfigurationProvider.java (revision ) @@ -53,5 +53,4 @@ * @throws ConfigurationException */ HierarchicalConfiguration getConfiguration(String beanName) throws ConfigurationException; - } Index: server/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/ReloadableManagementMBean.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- server/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/ReloadableManagementMBean.java (revision ) +++ server/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/ReloadableManagementMBean.java (revision ) @@ -0,0 +1,9 @@ +package org.apache.james.mailetcontainer.impl; + +/** + * Allow to reload {@link org.apache.james.mailetcontainer.api.MailProcessor} via JMX + */ +public interface ReloadableManagementMBean { + + void reload() throws Exception; +} Index: server/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/lib/AbstractReloadableProcessor.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- server/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/lib/AbstractReloadableProcessor.java (revision ) +++ server/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/lib/AbstractReloadableProcessor.java (revision ) @@ -0,0 +1,103 @@ +package org.apache.james.mailetcontainer.lib; + +import org.apache.commons.configuration.HierarchicalConfiguration; +import org.apache.james.lifecycle.api.Configurable; +import org.apache.james.lifecycle.api.LogEnabled; +import org.apache.james.mailetcontainer.api.MailProcessor; +import org.apache.james.mailetcontainer.impl.ReloadableManagementMBean; +import org.apache.mailet.Mail; +import org.slf4j.Logger; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.mail.MessagingException; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Abstract class for {@link MailProcessor} that allow one to hot-reload {@link MailProcessor} by delegating + * {@link #service(org.apache.mailet.Mail)} to another MailProcessor. + */ +public abstract class AbstractReloadableProcessor implements MailProcessor, ReloadableManagementMBean, Configurable, LogEnabled { + + private MailProcessor delegate; + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private HierarchicalConfiguration config; + private Logger logger; + + public void configure(HierarchicalConfiguration config) { + this.config = config; + } + + public void setLog(Logger log) { + this.logger = log; + } + + public void service(Mail mail) throws MessagingException { + try { + lock.readLock().lock(); + delegate.service(mail); + } finally { + lock.readLock().unlock(); + } + } + + public void reload() throws Exception { + try { + lock.writeLock().lock(); + destroyDelegate(); + loadDelegate(config); + } finally { + lock.writeLock().unlock(); + } + } + + @PreDestroy + public void destroy() { + try { + lock.writeLock().lock(); + destroyDelegate(); + } finally { + lock.writeLock().unlock(); + } + } + + /** + * Destroys underlying {@link MailProcessor} + */ + protected abstract void destroyDelegate(); + + protected MailProcessor getDelegate() { + return delegate; + } + + protected void setDelegate(MailProcessor delegate) { + this.delegate = delegate; + } + + protected ReadWriteLock getDelegateLock() { + return lock; + } + + protected Logger getLogger() { + return logger; + } + + @PostConstruct + public void init() throws Exception { + try { + lock.writeLock().lock(); + loadDelegate(config); + } finally { + lock.writeLock().unlock(); + } + } + + /** + * Loads new backing delegate {@link MailProcessor} + * + * @param config configuration + * @throws Exception + */ + protected abstract void loadDelegate(HierarchicalConfiguration config) throws Exception; +} Index: server/lifecycle-spring/src/main/java/org/apache/james/container/spring/lifecycle/ConfigurationProviderManagementMBean.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- server/lifecycle-spring/src/main/java/org/apache/james/container/spring/lifecycle/ConfigurationProviderManagementMBean.java (revision ) +++ server/lifecycle-spring/src/main/java/org/apache/james/container/spring/lifecycle/ConfigurationProviderManagementMBean.java (revision ) @@ -0,0 +1,11 @@ +package org.apache.james.container.spring.lifecycle; + +import org.apache.commons.configuration.ConfigurationException; + +/** + * Allow to reload registered configuration via JMX + */ +public interface ConfigurationProviderManagementMBean{ + + void reloadConfiguration(String beanName) throws ConfigurationException; +} Index: server/lifecycle-spring/src/main/java/org/apache/james/container/spring/lifecycle/ConfigurationProviderImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- server/lifecycle-spring/src/main/java/org/apache/james/container/spring/lifecycle/ConfigurationProviderImpl.java (revision 1340913) +++ server/lifecycle-spring/src/main/java/org/apache/james/container/spring/lifecycle/ConfigurationProviderImpl.java (revision ) @@ -25,6 +25,7 @@ import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.HierarchicalConfiguration; +import org.apache.commons.configuration.SubnodeConfiguration; import org.apache.commons.configuration.XMLConfiguration; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ResourceLoaderAware; @@ -34,7 +35,7 @@ /** * Register Configuration and act as Configuration Provider. */ -public class ConfigurationProviderImpl implements ConfigurationProvider, ResourceLoaderAware, InitializingBean { +public class ConfigurationProviderImpl implements ConfigurationProvider, ConfigurationProviderManagementMBean, ResourceLoaderAware, InitializingBean { private static final String CONFIGURATION_FILE_SUFFIX = ".conf"; /** @@ -123,7 +124,7 @@ try { HierarchicalConfiguration config = getConfig(resource); if (configPart != null) { - return config.configurationAt(configPart); + return config.configurationAt(configPart, true); } else { return config; } @@ -137,6 +138,47 @@ // Configuration was not loaded, throw exception. throw new ConfigurationException("Unable to load configuration for component " + name); + } + + public void reloadConfiguration(String beanName) throws ConfigurationException { + if(configurationMappings == null || !configurationMappings.containsKey(beanName)){ + return; + } + + HierarchicalConfiguration conf = configurations.get(beanName); + String name = configurationMappings.get(beanName); + + int i = name.indexOf("."); + String resourceName; + String configPart = null; + + if (i > -1) { + resourceName = name.substring(0, i); + configPart = name.substring(i + 1); + } else { + resourceName = name; + } + + Resource resource = loader.getResource(getConfigPrefix() + resourceName + CONFIGURATION_FILE_SUFFIX); + + if(!resource.exists()){ + throw new ConfigurationException("Unable to load configuration for component " + name); + } + + if(configPart != null){ + conf = ((SubnodeConfiguration) conf).getParent(); + } + + XMLConfiguration xmlConf = (XMLConfiguration) conf; + try { + synchronized (xmlConf.getReloadLock()){ + xmlConf.clear(); + xmlConf.load(resource.getInputStream()); + } + } catch (Exception e) { + throw new ConfigurationException("Unable to load configuration for component " + name, e); + } + return; } /** Index: server/pom.xml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>ISO-8859-15 =================================================================== --- server/pom.xml (revision 1340913) +++ server/pom.xml (revision ) @@ -728,7 +728,7 @@ commons-configuration commons-configuration - 1.6 + 1.7 commons-digester Index: server/mailetcontainer-camel/src/test/java/org/apache/james/mailetcontainer/lib/AbstractReloadableProcessorTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- server/mailetcontainer-camel/src/test/java/org/apache/james/mailetcontainer/lib/AbstractReloadableProcessorTest.java (revision ) +++ server/mailetcontainer-camel/src/test/java/org/apache/james/mailetcontainer/lib/AbstractReloadableProcessorTest.java (revision ) @@ -0,0 +1,72 @@ +package org.apache.james.mailetcontainer.lib; + +import org.apache.commons.configuration.HierarchicalConfiguration; +import org.apache.james.core.MailImpl; +import org.apache.james.mailetcontainer.api.MailProcessor; +import org.apache.mailet.Mail; +import org.junit.Test; + +import javax.mail.MessagingException; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class AbstractReloadableProcessorTest { + + private class ReloadableProcessorImpl extends AbstractReloadableProcessor { + private MailProcessor processor; + + public void setProcessor(MailProcessor processor) { + this.processor = processor; + } + + protected void destroyDelegate() { + } + + protected void loadDelegate(HierarchicalConfiguration config) throws Exception { + setDelegate(processor); + } + } + + private class MailProcessorImpl implements MailProcessor { + private boolean called = false; + + public void service(Mail mail) throws MessagingException { + called = true; + } + + public boolean isCalled() { + return called; + } + } + + @Test + public void testDelegate() throws Exception { + ReloadableProcessorImpl proxy = new ReloadableProcessorImpl(); + MailProcessorImpl processor = new MailProcessorImpl(); + proxy.setProcessor(processor); + proxy.init(); + + Mail mail = new MailImpl(); + proxy.service(mail); + + assertTrue(processor.isCalled()); + } + + @Test + public void testReload() throws Exception { + ReloadableProcessorImpl proxy = new ReloadableProcessorImpl(); + MailProcessorImpl processor = new MailProcessorImpl(); + MailProcessorImpl anotherProcessor = new MailProcessorImpl(); + proxy.setProcessor(processor); + proxy.init(); + + proxy.setProcessor(anotherProcessor); + proxy.reload(); + Mail mail = new MailImpl(); + proxy.service(mail); + + assertFalse(processor.isCalled()); + assertTrue(anotherProcessor.isCalled()); + } +}