Index: server/trunk/container-spring/src/main/java/org/apache/james/container/spring/bean/Reloadable.java =================================================================== --- server/trunk/container-spring/src/main/java/org/apache/james/container/spring/bean/Reloadable.java (revision ) +++ server/trunk/container-spring/src/main/java/org/apache/james/container/spring/bean/Reloadable.java (revision ) @@ -0,0 +1,27 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you 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.james.container.spring.bean; + +/** + * JMX MBean for reloadable object + */ +public interface Reloadable { + boolean reload(); +} Index: server/trunk/container-spring/src/test/java/org/apache/james/container/spring/bean/ReloadableMailProcessorProxyTest.java =================================================================== --- server/trunk/container-spring/src/test/java/org/apache/james/container/spring/bean/ReloadableMailProcessorProxyTest.java (revision ) +++ server/trunk/container-spring/src/test/java/org/apache/james/container/spring/bean/ReloadableMailProcessorProxyTest.java (revision ) @@ -0,0 +1,115 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you 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.james.container.spring.bean; + +import junit.framework.TestCase; +import org.apache.james.mailetcontainer.api.MailProcessor; +import org.apache.mailet.Mail; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor; +import org.springframework.context.support.GenericApplicationContext; + +import javax.annotation.PreDestroy; +import javax.mail.MessagingException; + +public class ReloadableMailProcessorProxyTest extends TestCase { + + // ------------------------------ FIELDS ------------------------------ + + ReloadableMailProcessorProxy proxy; + private GenericApplicationContext ctx; + private String name = "testBean"; + + // -------------------------- OTHER METHODS -------------------------- + + @Override + protected void setUp() throws Exception { + super.setUp(); + BeanDefinitionBuilder bdBuilder = BeanDefinitionBuilder.genericBeanDefinition(ReloadableMailProcessorProxy.class); + bdBuilder.addPropertyValue("processorClass", "org.apache.james.container.spring.bean.ReloadableMailProcessorProxyTest$MockMailProcessor"); + BeanDefinitionBuilder annotationBuilder = BeanDefinitionBuilder.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class); + ctx = new GenericApplicationContext(); + ctx.registerBeanDefinition("CommonAnnotationBeanPostProcessor", annotationBuilder.getBeanDefinition()); + ctx.registerBeanDefinition(name, bdBuilder.getBeanDefinition()); + ctx.refresh(); + proxy = (ReloadableMailProcessorProxy) ctx.getBean("testBean"); + proxy.setLog(LoggerFactory.getLogger("MockLogger")); + } + + public void testAppliedWithProxyName() throws Exception { + assertEquals(name, ((MockMailProcessor) proxy.getProcessor()).getBeanName()); + } + + public void testInvokePreDestroy() throws Exception { + MockMailProcessor mock = (MockMailProcessor) proxy.getProcessor(); + proxy.dispose(); + assertTrue(mock.isDestroyCalled()); + } + + public void testReload() throws Exception { + MockMailProcessor processor = (MockMailProcessor) proxy.getProcessor(); + proxy.reload(); + assertTrue(processor.isDestroyCalled()); + assertNotSame(processor, proxy.getProcessor()); + assertEquals(name, ((MockMailProcessor) proxy.getProcessor()).getBeanName()); + } + + public void testServiceDelegated() throws Exception { + proxy.service(null); + assertTrue(((MockMailProcessor) proxy.getProcessor()).isServiceCalled()); + } + + // -------------------------- INNER CLASSES -------------------------- + + static class MockMailProcessor implements MailProcessor, BeanNameAware { + private boolean serviceCalled = false; + private boolean destroyCalled = false; + private String beanName; + + public boolean isServiceCalled() { + return serviceCalled; + } + + public boolean isDestroyCalled() { + return destroyCalled; + } + + @Override + public void setBeanName(String name) { + beanName = name; + } + + public String getBeanName() { + return beanName; + } + + @Override + public void service(Mail mail) throws MessagingException { + serviceCalled = true; + } + + @PreDestroy + public void dispose() { + destroyCalled = true; + } + } +} Index: server/trunk/container-spring/src/main/java/org/apache/james/container/spring/bean/ReloadableMailProcessorProxy.java =================================================================== --- server/trunk/container-spring/src/main/java/org/apache/james/container/spring/bean/ReloadableMailProcessorProxy.java (revision ) +++ server/trunk/container-spring/src/main/java/org/apache/james/container/spring/bean/ReloadableMailProcessorProxy.java (revision ) @@ -0,0 +1,186 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you 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.james.container.spring.bean; + +import org.apache.james.lifecycle.api.LogEnabled; +import org.apache.james.mailetcontainer.api.MailProcessor; +import org.apache.mailet.Mail; +import org.slf4j.Logger; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.mail.MessagingException; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import static org.springframework.util.ReflectionUtils.makeAccessible; + +public class ReloadableMailProcessorProxy extends AbstractBeanFactory implements MailProcessor, BeanNameAware, LogEnabled, Reloadable { + + // ------------------------------ FIELDS ------------------------------ + + /** + * Class name of proxy'ed object. + */ + private String processorClass; + + /** + * Bean name of this object, allowing Spring initialize proxy'ed object as + * having the same name. + */ + private String beanName; + + /** + * Real processor. + */ + private MailProcessor processor; + + private Logger logger; + private AtomicBoolean ready = new AtomicBoolean(false); + private ReentrantReadWriteLock reloadLock = new ReentrantReadWriteLock(true); + + // --------------------- GETTER / SETTER METHODS --------------------- + + public MailProcessor getProcessor() { + return processor; + } + + public boolean isReady() { + return ready.get(); + } + + @Override + public void setBeanName(String name) { + this.beanName = name; + } + + @Override + public void setLog(Logger log) { + this.logger = log; + } + + public void setProcessorClass(String processorClass) { + this.processorClass = processorClass; + } + + // -------------- INTERFACE METHODS -------------- + + // ----------- Interface MailProcessor ----------- + + @Override + public void service(Mail mail) throws MessagingException { + try { + reloadLock.readLock().lock(); + if (ready.get()) { + processor.service(mail); + } else { + throw new MessagingException("Mail processor " + processorClass + " is not ready."); + } + } finally { + reloadLock.readLock().unlock(); + } + } + + // ----------- Interface Reloadable ----------- + + /** + * Disposes current {@link MailProcessor} and creates a new one. If creation + * fails, following {@link #service(org.apache.mailet.Mail)} calls will + * throws exception. + * + * @return True if reload successfully, false otherwise. + */ + @Override + public boolean reload() { + try { + reloadLock.writeLock().lock(); + processPreDestroy(processor); + try { + processor = load(); + } catch (Exception e) { + logger.warn("Unable to reload mail processor " + processorClass, e); + ready.set(false); + return false; + } + logger.info(processorClass + " reloaded"); + return true; + } finally { + reloadLock.writeLock().unlock(); + } + } + + // -------------------------- OTHER METHODS -------------------------- + + @PostConstruct + public void init() throws ClassNotFoundException { + processor = load(); + ready.set(true); + } + + @PreDestroy + public void dispose() { + ready.set(false); + if (processor != null) { + processPreDestroy(processor); + } + } + + /** + * Loads a {@link MailProcessor} bean of class {@link #beanName} and + * populates it with the bean id of this object. + * + * @return The new {@link MailProcessor} instance + * @throws ClassNotFoundException + * If the processor class was not found + */ + @SuppressWarnings("unchecked") + private MailProcessor load() throws ClassNotFoundException { + Class c = (Class) getBeanFactory().getBeanClassLoader().loadClass(processorClass); + MailProcessor processor = (MailProcessor) getBeanFactory().autowire(c, AutowireCapableBeanFactory.AUTOWIRE_NO, false); + getBeanFactory().initializeBean(processor, beanName); + return processor; + } + + /** + * Invokes {@link PreDestroy} methods on given {@link MailProcessor} + * + * @param target + * The object to be invoked on. + */ + private void processPreDestroy(MailProcessor target) { + for (Method method : target.getClass().getMethods()) { + if (method.getAnnotation(PreDestroy.class) != null) { + makeAccessible(method); + try { + method.invoke(target, (Object[]) null); + } catch (InvocationTargetException e) { + logger.warn("Invocation of PreDestroy method failed on " + processorClass, e); + } catch (IllegalAccessException e) { + logger.error("Unable to invoke PreDestroy method on " + processorClass, e); + } + } + } + } +}