diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java index 00cc52c953a95e5f5aba308a47c447b8ac279674..b40addd1936082918b4a74e9d5515c6e342c07c7 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java @@ -190,6 +190,8 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe private int startupDelay = 0; + private int shutdownOrder = Integer.MAX_VALUE; + private boolean exposeSchedulerInRepository = false; private boolean waitForJobsToCompleteOnShutdown = false; @@ -373,6 +375,21 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe return this.autoStartup; } + /** + * Specify the order in which this scheduler should be stopped. + * By default it will be stopped in the last group. + */ + public void setShutdownOrder(int shutdownOrder) { + this.shutdownOrder = shutdownOrder; + } + + /** + * Return the order in which this scheduler will be stopped. + */ + public int getShutdownOrder() { + return this.shutdownOrder; + } + /** * Set the number of seconds to wait after initialization before * starting the scheduler asynchronously. Default is 0, meaning @@ -708,6 +725,11 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe } } + public void stop(Runnable callback) throws SchedulingException { + this.stop(); + callback.run(); + } + public boolean isRunning() throws SchedulingException { if (this.scheduler != null) { try { diff --git a/org.springframework.context/src/main/java/org/springframework/context/LifecycleProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/LifecycleProcessor.java index aba0cb205b74ebadbabc63944ef4ae15fe69e1d9..a903fee9028a027c4665d1a5e75e677cacd4d81f 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/LifecycleProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/LifecycleProcessor.java @@ -17,6 +17,8 @@ package org.springframework.context; /** + * Strategy interface for processing Lifecycle beans within the ApplicationContext. + * * @author Mark Fisher * @since 3.0 */ diff --git a/org.springframework.context/src/main/java/org/springframework/context/SmartLifecycle.java b/org.springframework.context/src/main/java/org/springframework/context/SmartLifecycle.java index e0b4fd46867fe33fd7954eeaca06a4d23616968e..f0acebb8eddbb8aa6c3a42b9747fbb72dd7c89e9 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/SmartLifecycle.java +++ b/org.springframework.context/src/main/java/org/springframework/context/SmartLifecycle.java @@ -17,6 +17,9 @@ package org.springframework.context; /** + * An extension of the Lifecycle interface for those beans that require to be + * started upon ApplicationContext refresh and/or shutdown in a particular order. + * * @author Mark Fisher * @since 3.0 */ @@ -24,4 +27,8 @@ public interface SmartLifecycle extends Lifecycle { boolean isAutoStartup(); + int getShutdownOrder(); + + void stop(Runnable callback); + } diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java index 59cf544f0f374c3707dfbba51b2b86b513ed1544..027536f8f90d915be29ac8bec35b41ee311f9acb 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java @@ -16,9 +16,18 @@ package org.springframework.context.support; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; @@ -29,16 +38,31 @@ import org.springframework.context.SmartLifecycle; import org.springframework.util.Assert; /** + * Default implementation of the {@link LifecycleProcessor} strategy. + * * @author Mark Fisher * @since 3.0 */ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactoryAware { + private final Log logger = LogFactory.getLog(this.getClass()); + + private volatile long shutdownGroupTimeout = 30000; + private volatile boolean running; private volatile ConfigurableListableBeanFactory beanFactory; + /** + * Specify the maximum time allotted for the shutdown of any group of + * SmartLifecycle beans (those with the same 'order' value). The default + * value is 30 seconds. + */ + public void setShutdownGroupTimeout(long shutdownGroupTimeout) { + this.shutdownGroupTimeout = shutdownGroupTimeout; + } + public void setBeanFactory(BeanFactory beanFactory) { Assert.isTrue(beanFactory instanceof ConfigurableListableBeanFactory, "A ConfigurableListableBeanFactory is required."); @@ -63,8 +87,23 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor public void stop() { Map lifecycleBeans = getLifecycleBeans(); - for (String beanName : new LinkedHashSet(lifecycleBeans.keySet())) { - doStop(lifecycleBeans, beanName); + Map shutdownGroups = new HashMap(); + for (Map.Entry entry : lifecycleBeans.entrySet()) { + Lifecycle lifecycle = entry.getValue(); + int shutdownOrder = getShutdownOrder(lifecycle); + ShutdownGroup group = shutdownGroups.get(shutdownOrder); + if (group == null) { + group = new ShutdownGroup(shutdownOrder, this.shutdownGroupTimeout, lifecycleBeans); + shutdownGroups.put(shutdownOrder, group); + } + group.add(entry.getKey(), lifecycle); + } + if (shutdownGroups.size() > 0) { + List keys = new ArrayList(shutdownGroups.keySet()); + Collections.sort(keys); + for (Integer key : keys) { + shutdownGroups.get(key).shutdown(); + } } this.running = false; } @@ -112,15 +151,24 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor * @param lifecycleBeans Map with bean name as key and Lifecycle instance as value * @param beanName the name of the bean to stop */ - private void doStop(Map lifecycleBeans, String beanName) { + private void doStop(Map lifecycleBeans, String beanName, final CountDownLatch latch) { Lifecycle bean = lifecycleBeans.get(beanName); - if (bean != null && !this.equals(bean)) { + if (bean != null) { String[] dependentBeans = this.beanFactory.getDependentBeans(beanName); for (String dependentBean : dependentBeans) { - doStop(lifecycleBeans, dependentBean); + doStop(lifecycleBeans, dependentBean, latch); } if (bean.isRunning()) { - bean.stop(); + if (bean instanceof SmartLifecycle) { + ((SmartLifecycle) bean).stop(new Runnable() { + public void run() { + latch.countDown(); + } + }); + } + else { + bean.stop(); + } } lifecycleBeans.remove(beanName); } @@ -131,7 +179,7 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor Map beans = new LinkedHashMap(); for (String beanName : beanNames) { Object bean = beanFactory.getSingleton(beanName); - if (bean instanceof Lifecycle) { + if (bean instanceof Lifecycle && !this.equals(bean)) { beans.put(beanName, (Lifecycle) bean); } } @@ -150,4 +198,90 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor return beans; } + + private static int getShutdownOrder(Lifecycle bean) { + return (bean instanceof SmartLifecycle) ? + ((SmartLifecycle) bean).getShutdownOrder() : Integer.MAX_VALUE; + } + + + /** + * Helper class for maintaining a group of Lifecycle beans that should be shutdown + * together based on their 'shutdownOrder' value (or the default MAX_INTEGER value). + */ + private class ShutdownGroup { + + private final List members = new ArrayList(); + + private Map lifecycleBeans = getLifecycleBeans(); + + private volatile int smartMemberCount; + + private final int order; + + private final long timeout; + + + ShutdownGroup(int order, long timeout, Map lifecycleBeans) { + this.order = order; + this.timeout = timeout; + this.lifecycleBeans = lifecycleBeans; + } + + void add(String name, Lifecycle bean) { + if (bean instanceof SmartLifecycle) { + this.smartMemberCount++; + } + this.members.add(new ShutdownGroupMember(name, bean)); + } + + void shutdown() { + if (members.size() == 0) { + return; + } + Collections.sort(members); + final CountDownLatch latch = new CountDownLatch(this.smartMemberCount); + for (ShutdownGroupMember member : members) { + if (lifecycleBeans.containsKey(member.name)) { + doStop(lifecycleBeans, member.name, latch); + } + else if (member.bean instanceof SmartLifecycle) { + // already removed, must have been a dependent + latch.countDown(); + } + } + try { + latch.await(this.timeout, TimeUnit.MILLISECONDS); + if (latch.getCount() != 0) { + if (logger.isWarnEnabled()) { + logger.warn("failed to shutdown beans with order " + + this.order + " within timeout of " + this.timeout); + } + } + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + + private static class ShutdownGroupMember implements Comparable { + + private String name; + + private Lifecycle bean; + + ShutdownGroupMember(String name, Lifecycle bean) { + this.name = name; + this.bean = bean; + } + + public int compareTo(ShutdownGroupMember other) { + int thisOrder = getShutdownOrder(this.bean); + int otherOrder = getShutdownOrder(other.bean); + return (thisOrder == otherOrder) ? 0 : (thisOrder < otherOrder) ? -1 : 1; + } + } + } diff --git a/org.springframework.context/src/test/java/org/springframework/context/support/DefaultLifecycleProcessorTests.java b/org.springframework.context/src/test/java/org/springframework/context/support/DefaultLifecycleProcessorTests.java new file mode 100644 index 0000000000000000000000000000000000000000..6b7c1c51db511316893c76562e5b418ee7bca12d --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/support/DefaultLifecycleProcessorTests.java @@ -0,0 +1,271 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * 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.springframework.context.support; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.CopyOnWriteArrayList; + +import org.junit.Test; + +import org.springframework.context.Lifecycle; +import org.springframework.context.SmartLifecycle; + +/** + * @author Mark Fisher + * @since 3.0 + */ +public class DefaultLifecycleProcessorTests { + + @Test + public void smartLifecycleGroupShutdown() throws Exception { + CopyOnWriteArrayList stoppedBeans = new CopyOnWriteArrayList(); + TestSmartLifecycleBean bean1 = new TestSmartLifecycleBean(1, 300, stoppedBeans); + TestSmartLifecycleBean bean2 = new TestSmartLifecycleBean(3, 100, stoppedBeans); + TestSmartLifecycleBean bean3 = new TestSmartLifecycleBean(1, 600, stoppedBeans); + TestSmartLifecycleBean bean4 = new TestSmartLifecycleBean(2, 400, stoppedBeans); + TestSmartLifecycleBean bean5 = new TestSmartLifecycleBean(2, 700, stoppedBeans); + TestSmartLifecycleBean bean6 = new TestSmartLifecycleBean(Integer.MAX_VALUE, 200, stoppedBeans); + TestSmartLifecycleBean bean7 = new TestSmartLifecycleBean(3, 200, stoppedBeans); + StaticApplicationContext context = new StaticApplicationContext(); + context.getBeanFactory().registerSingleton("bean1", bean1); + context.getBeanFactory().registerSingleton("bean2", bean2); + context.getBeanFactory().registerSingleton("bean3", bean3); + context.getBeanFactory().registerSingleton("bean4", bean4); + context.getBeanFactory().registerSingleton("bean5", bean5); + context.getBeanFactory().registerSingleton("bean6", bean6); + context.getBeanFactory().registerSingleton("bean7", bean7); + context.refresh(); + context.stop(); + assertEquals(1, getShutdownOrder(stoppedBeans.get(0))); + assertEquals(1, getShutdownOrder(stoppedBeans.get(1))); + assertEquals(2, getShutdownOrder(stoppedBeans.get(2))); + assertEquals(2, getShutdownOrder(stoppedBeans.get(3))); + assertEquals(3, getShutdownOrder(stoppedBeans.get(4))); + assertEquals(3, getShutdownOrder(stoppedBeans.get(5))); + assertEquals(Integer.MAX_VALUE, getShutdownOrder(stoppedBeans.get(6))); + } + + @Test + public void singleSmartLifecycleShutdown() throws Exception { + CopyOnWriteArrayList stoppedBeans = new CopyOnWriteArrayList(); + TestSmartLifecycleBean bean = new TestSmartLifecycleBean(99, 300, stoppedBeans); + StaticApplicationContext context = new StaticApplicationContext(); + context.getBeanFactory().registerSingleton("bean", bean); + context.refresh(); + assertTrue(bean.isRunning()); + context.stop(); + assertEquals(1, stoppedBeans.size()); + assertFalse(bean.isRunning()); + assertEquals(bean, stoppedBeans.get(0)); + } + + @Test + public void singleLifecycleShutdown() throws Exception { + CopyOnWriteArrayList stoppedBeans = new CopyOnWriteArrayList(); + Lifecycle bean = new TestLifecycleBean(stoppedBeans); + StaticApplicationContext context = new StaticApplicationContext(); + context.getBeanFactory().registerSingleton("bean", bean); + context.refresh(); + assertFalse(bean.isRunning()); + bean.start(); + assertTrue(bean.isRunning()); + context.stop(); + assertEquals(1, stoppedBeans.size()); + assertFalse(bean.isRunning()); + assertEquals(bean, stoppedBeans.get(0)); + } + + @Test + public void mixedShutdown() throws Exception { + CopyOnWriteArrayList stoppedBeans = new CopyOnWriteArrayList(); + Lifecycle bean1 = new TestLifecycleBean(stoppedBeans); + Lifecycle bean2 = new TestSmartLifecycleBean(500, 200, stoppedBeans); + Lifecycle bean3 = new TestSmartLifecycleBean(Integer.MAX_VALUE, 100, stoppedBeans); + Lifecycle bean4 = new TestLifecycleBean(stoppedBeans); + Lifecycle bean5 = new TestSmartLifecycleBean(1, 200, stoppedBeans); + StaticApplicationContext context = new StaticApplicationContext(); + context.getBeanFactory().registerSingleton("bean1", bean1); + context.getBeanFactory().registerSingleton("bean2", bean2); + context.getBeanFactory().registerSingleton("bean3", bean3); + context.getBeanFactory().registerSingleton("bean4", bean4); + context.getBeanFactory().registerSingleton("bean5", bean5); + context.refresh(); + assertFalse(bean1.isRunning()); + assertFalse(bean4.isRunning()); + bean1.start(); + bean4.start(); + assertTrue(bean1.isRunning()); + assertTrue(bean2.isRunning()); + assertTrue(bean3.isRunning()); + assertTrue(bean4.isRunning()); + assertTrue(bean5.isRunning()); + context.stop(); + assertFalse(bean1.isRunning()); + assertFalse(bean2.isRunning()); + assertFalse(bean3.isRunning()); + assertFalse(bean4.isRunning()); + assertFalse(bean5.isRunning()); + assertEquals(5, stoppedBeans.size()); + assertEquals(1, getShutdownOrder(stoppedBeans.get(0))); + assertEquals(500, getShutdownOrder(stoppedBeans.get(1))); + assertEquals(Integer.MAX_VALUE, getShutdownOrder(stoppedBeans.get(2))); + assertEquals(Integer.MAX_VALUE, getShutdownOrder(stoppedBeans.get(3))); + assertEquals(Integer.MAX_VALUE, getShutdownOrder(stoppedBeans.get(4))); + } + + @Test + public void dependantShutdownFirstEvenIfItsOrderIsHigher() throws Exception { + CopyOnWriteArrayList stoppedBeans = new CopyOnWriteArrayList(); + TestSmartLifecycleBean bean1 = new TestSmartLifecycleBean(1, 200, stoppedBeans); + TestSmartLifecycleBean bean99 = new TestSmartLifecycleBean(99, 100, stoppedBeans); + TestSmartLifecycleBean bean2 = new TestSmartLifecycleBean(2, 300, stoppedBeans); + TestSmartLifecycleBean bean7 = new TestSmartLifecycleBean(7, 400, stoppedBeans); + TestSmartLifecycleBean beanLast = new TestSmartLifecycleBean(Integer.MAX_VALUE, 400, stoppedBeans); + StaticApplicationContext context = new StaticApplicationContext(); + context.getBeanFactory().registerSingleton("bean1", bean1); + context.getBeanFactory().registerSingleton("bean2", bean2); + context.getBeanFactory().registerSingleton("bean7", bean7); + context.getBeanFactory().registerSingleton("bean99", bean99); + context.getBeanFactory().registerSingleton("beanLast", beanLast); + context.getBeanFactory().registerDependentBean("bean2", "bean99"); + context.refresh(); + assertTrue(bean1.isRunning()); + assertTrue(bean7.isRunning()); + assertTrue(bean99.isRunning()); + context.stop(); + assertFalse(bean1.isRunning()); + assertFalse(bean7.isRunning()); + assertFalse(bean99.isRunning()); + assertEquals(5, stoppedBeans.size()); + assertEquals(1, getShutdownOrder(stoppedBeans.get(0))); + assertEquals(99, getShutdownOrder(stoppedBeans.get(1))); + assertEquals(2, getShutdownOrder(stoppedBeans.get(2))); + assertEquals(7, getShutdownOrder(stoppedBeans.get(3))); + assertEquals(Integer.MAX_VALUE, getShutdownOrder(stoppedBeans.get(4))); + } + + @Test + public void dependantShutdownFirstEvenIfNotSmartLifecycle() throws Exception { + CopyOnWriteArrayList stoppedBeans = new CopyOnWriteArrayList(); + TestSmartLifecycleBean bean1 = new TestSmartLifecycleBean(1, 200, stoppedBeans); + TestLifecycleBean simpleBean = new TestLifecycleBean(stoppedBeans); + TestSmartLifecycleBean bean2 = new TestSmartLifecycleBean(2, 300, stoppedBeans); + TestSmartLifecycleBean bean7 = new TestSmartLifecycleBean(7, 400, stoppedBeans); + TestSmartLifecycleBean beanLast = new TestSmartLifecycleBean(Integer.MAX_VALUE, 400, stoppedBeans); + StaticApplicationContext context = new StaticApplicationContext(); + context.getBeanFactory().registerSingleton("bean1", bean1); + context.getBeanFactory().registerSingleton("bean2", bean2); + context.getBeanFactory().registerSingleton("bean7", bean7); + context.getBeanFactory().registerSingleton("simpleBean", simpleBean); + context.getBeanFactory().registerSingleton("beanLast", beanLast); + context.getBeanFactory().registerDependentBean("bean2", "simpleBean"); + context.refresh(); + assertTrue(bean1.isRunning()); + assertTrue(bean7.isRunning()); + assertFalse(simpleBean.isRunning()); + simpleBean.start(); + assertTrue(simpleBean.isRunning()); + context.stop(); + assertFalse(bean1.isRunning()); + assertFalse(bean7.isRunning()); + assertFalse(simpleBean.isRunning()); + assertEquals(5, stoppedBeans.size()); + assertEquals(1, getShutdownOrder(stoppedBeans.get(0))); + assertEquals(Integer.MAX_VALUE, getShutdownOrder(stoppedBeans.get(1))); + assertEquals(2, getShutdownOrder(stoppedBeans.get(2))); + assertEquals(7, getShutdownOrder(stoppedBeans.get(3))); + assertEquals(Integer.MAX_VALUE, getShutdownOrder(stoppedBeans.get(4))); + } + + + private static int getShutdownOrder(Lifecycle lifecycle) { + return (lifecycle instanceof SmartLifecycle) ? + ((SmartLifecycle) lifecycle).getShutdownOrder() : Integer.MAX_VALUE; + } + + + private class TestLifecycleBean implements Lifecycle { + + protected final CopyOnWriteArrayList stoppedBeans; + + private volatile boolean running; + + + TestLifecycleBean(CopyOnWriteArrayList stoppedBeans) { + this.stoppedBeans = stoppedBeans; + } + + public boolean isRunning() { + return this.running; + } + + public void start() { + this.running = true; + } + + public void stop() { + this.stoppedBeans.add(this); + this.running = false; + } + + } + + + private class TestSmartLifecycleBean extends TestLifecycleBean implements SmartLifecycle { + + private final int shutdownOrder; + + private final int shutdownDelay; + + + TestSmartLifecycleBean(int shutdownOrder, int shutdownDelay, CopyOnWriteArrayList stoppedBeans) { + super(stoppedBeans); + this.shutdownOrder = shutdownOrder; + this.shutdownDelay = shutdownDelay; + } + + public int getShutdownOrder() { + return this.shutdownOrder; + } + + public boolean isAutoStartup() { + return true; + } + + public void stop(final Runnable callback) { + final int delay = this.shutdownDelay; + new Thread(new Runnable() { + public void run() { + try { + Thread.sleep(delay); + stop(); + } + catch (InterruptedException e) { + // ignore + } + finally { + callback.run(); + } + } + }).start(); + } + } + +} diff --git a/org.springframework.jms/src/main/java/org/springframework/jms/listener/AbstractJmsListeningContainer.java b/org.springframework.jms/src/main/java/org/springframework/jms/listener/AbstractJmsListeningContainer.java index 69ed8db595ef12749711f7191230502ffa93481b..a3d5e01536479e8b9880cd241be9cb486c6db564 100644 --- a/org.springframework.jms/src/main/java/org/springframework/jms/listener/AbstractJmsListeningContainer.java +++ b/org.springframework.jms/src/main/java/org/springframework/jms/listener/AbstractJmsListeningContainer.java @@ -65,6 +65,8 @@ public abstract class AbstractJmsListeningContainer extends JmsDestinationAccess private boolean autoStartup = true; + private int shutdownOrder = Integer.MAX_VALUE; + private String beanName; private Connection sharedConnection; @@ -116,6 +118,21 @@ public abstract class AbstractJmsListeningContainer extends JmsDestinationAccess return this.autoStartup; } + /** + * Specify the order in which this container should be stopped. + * By default it will be stopped in the last group. + */ + public void setShutdownOrder(int shutdownOrder) { + this.shutdownOrder = shutdownOrder; + } + + /** + * Return the order in which this container will be stopped. + */ + public int getShutdownOrder() { + return this.shutdownOrder; + } + public void setBeanName(String beanName) { this.beanName = beanName; } @@ -286,6 +303,11 @@ public abstract class AbstractJmsListeningContainer extends JmsDestinationAccess } } + public void stop(Runnable callback) { + this.stop(); + callback.run(); + } + /** * Notify all invoker tasks and stop the shared Connection, if any. * @throws JMSException if thrown by JMS API methods diff --git a/org.springframework.jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java b/org.springframework.jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java index d366f9d018bbd25202b328ac6158a04da4d4e654..58224a22d2890c361dbe9034814b4f6cfe85d10e 100644 --- a/org.springframework.jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java +++ b/org.springframework.jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java @@ -514,6 +514,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe * @throws JmsException if stopping failed * @see #stop() */ + @Override public void stop(Runnable callback) throws JmsException { synchronized (this.lifecycleMonitor) { this.stopCallback = callback; diff --git a/org.springframework.transaction/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointManager.java b/org.springframework.transaction/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointManager.java index 66ba3dc18800e070a2ed569e7afff82fae175a0e..3993bcb425e2282c6de98909aa59dda14150e595 100644 --- a/org.springframework.transaction/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointManager.java +++ b/org.springframework.transaction/src/main/java/org/springframework/jca/endpoint/GenericMessageEndpointManager.java @@ -154,6 +154,8 @@ public class GenericMessageEndpointManager implements SmartLifecycle, Initializi private boolean autoStartup = true; + private int shutdownOrder = Integer.MAX_VALUE; + private boolean running = false; private final Object lifecycleMonitor = new Object(); @@ -226,6 +228,21 @@ public class GenericMessageEndpointManager implements SmartLifecycle, Initializi return this.autoStartup; } + /** + * Specify the order in which this endpoint manager should be stopped. + * By default it will be stopped in the last group. + */ + public void setShutdownOrder(int shutdownOrder) { + this.shutdownOrder = shutdownOrder; + } + + /** + * Return the order in which this endpoint manager will be stopped. + */ + public int getShutdownOrder() { + return this.shutdownOrder; + } + /** * Prepares the message endpoint, and automatically activates it * if the "autoStartup" flag is set to "true". @@ -280,6 +297,13 @@ public class GenericMessageEndpointManager implements SmartLifecycle, Initializi } } + public void stop(Runnable callback) { + synchronized (this.lifecycleMonitor) { + this.stop(); + callback.run(); + } + } + /** * Return whether the configured message endpoint is currently active. */