Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

INT-2485 Orderly Shutdown #403

Closed
wants to merge 4 commits into from

2 participants

Gary Russell Oleg Zhurakousky
Gary Russell
Collaborator

https://jira.springsource.org/browse/INT-2485

Initial commit - @ManagedOperation on IMBE

  • can be invoked via JMX, , or getting a reference to the IMBE from the application context.

INT-2485 Updates After Review Comments (JIRA)

  • Shutdown Schedulers first, and wait for them
  • Add a force parameter, which overrides thread pool shutdown options
  • Shutdown Sources/Channels after all thread pools have stopped
  • Mark other components as OrderlyShutdownCapable (e.g. JMS/AMQP Listener containers) and shut them down first
  • Wait for remaining time to allow for quiescence

Also

  • remove TimeUnit parameter (not JMX-friendly); time limit is now always milliseconds
  • If thread pools don't stop in time limit, force them down.

INT-2485 Handle Self-Destruction

Add shutdown-executor to IMBE.

When the shutdown was called on a Spring-Managed thread, the shutdown
was not clean because we timed out waiting for the current thread to
terminate. After that, we force terminated other components.

Now, by providing a dedicated Executor for the shutdown process, it
is used for the shutdown instead of the current thread. This Executor
is not shutdown.

It is not necessary to provide an Executor if the stopActiveComponents()
method is called on some other thread that is not involved in the
shutdown.

Also adds executor name to logs, when available.

INT-2485 Polishing

Fix MBean object name collision when running all tests.

INT-2485 Enable TCP Shutdown

Make TCP connection factories 'OrderlyShutdownCapable' so
they are stopped before schedulers/executors in order for
them to release any executor threads they are holding.

...tegration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
... ...
@@ -191,6 +220,15 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
191 220
 		this.beanFactory = (ListableBeanFactory) beanFactory;
192 221
 	}
193 222
 
  223
+	public void setApplicationContext(ApplicationContext applicationContext)
  224
+			throws BeansException {
  225
+		this.applicationContext = applicationContext;
1
Oleg Zhurakousky
olegz added a note May 08, 2012

null check

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...tegration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
... ...
@@ -191,6 +220,15 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
191 220
 		this.beanFactory = (ListableBeanFactory) beanFactory;
192 221
 	}
193 222
 
  223
+	public void setApplicationContext(ApplicationContext applicationContext)
  224
+			throws BeansException {
  225
+		this.applicationContext = applicationContext;
  226
+	}
  227
+
  228
+	public void setShutdownExecutor(Executor shutdownExecutor) {
  229
+		this.shutdownExecutor = shutdownExecutor;
1
Oleg Zhurakousky
olegz added a note May 08, 2012

null check

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Oleg Zhurakousky olegz commented on the diff May 08, 2012
...tegration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
((18 lines not shown))
  464
+	 * running tasks. Overrides any settings on schedulers/executors. When true
  465
+	 * may result in error messages being sent to error channels.
  466
+	 * @param howLong The time to wait in total for all activities to complete
  467
+	 * in milliseconds.
  468
+	 */
  469
+	@ManagedOperation
  470
+	public void stopActiveComponents(boolean force, long howLong) {
  471
+		if (!this.shuttingDown.compareAndSet(false, true)) {
  472
+			logger.error("Shutdown already in process");
  473
+			return;
  474
+		}
  475
+		this.shutdownDeadline = System.currentTimeMillis() + howLong;
  476
+		this.shutdownForced = force;
  477
+		if (this.shutdownExecutor == null) {
  478
+			try {
  479
+				logger.debug("Running shutdown on current thread");
3
Oleg Zhurakousky
olegz added a note May 08, 2012

We should do if(logger.isDebugEnabled)

Gary Russell Collaborator
garyrussell added a note May 08, 2012

That's only needed if when concatenating strings in the logger.debug(); when it's a simple string, it's ok (the first thing logger.debug() does is isDebugEnabled().

The reason we do it when concatenating is to avoid the overhead of the concatentation if isDebugEnabled() is false. In this case, the extra call to isDebugEnabled() is overhead.

Oleg Zhurakousky
olegz added a note May 08, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...tegration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
((148 lines not shown))
  594
+	}
  595
+
  596
+	@ManagedOperation
  597
+	public void stopExecutors() {
  598
+		if (logger.isDebugEnabled()) {
  599
+			logger.debug("Stopping executors" + (this.shutdownForced ? "(force)" : ""));
  600
+		}
  601
+		List<ExecutorService> executorServices = new ArrayList<ExecutorService>();
  602
+		Map<String, ThreadPoolTaskExecutor> executors = this.applicationContext
  603
+				.getBeansOfType(ThreadPoolTaskExecutor.class);
  604
+		for (Entry<String, ThreadPoolTaskExecutor> entry : executors.entrySet()) {
  605
+			ThreadPoolTaskExecutor executor = entry.getValue();
  606
+			if (executor == this.shutdownExecutor) {
  607
+				logger.debug("Skipping shutdown of shutdown executor");
  608
+				continue;
  609
+			}
1
Oleg Zhurakousky
olegz added a note May 08, 2012

I'll leave it up to you but wanted to say that I am not a big fan of 'continue'. Makes it hard to follow. May be something like this:

if (executor != this.shutdownExecutor) {
                if (logger.isInfoEnabled()) {
                    logger.info("Stopping executor " + executor.getThreadNamePrefix());
                }
                ExecutorService executorService = executor.getThreadPoolExecutor();
                executorServices.add(executorService);
                if (this.shutdownForced) {
                    executorService.shutdownNow();
                }
                else {
                    executorService.shutdown();
                }
            }
            else {
                logger.debug("Skipping shutdown of shutdown executor");
            }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...tegration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
((184 lines not shown))
  630
+		}
  631
+		List<ExecutorService> executorServices = new ArrayList<ExecutorService>();
  632
+		Map<String, ExecutorService> nonSpringExecutors = this.applicationContext
  633
+				.getBeansOfType(ExecutorService.class);
  634
+		for (Entry<String, ExecutorService> entry : nonSpringExecutors.entrySet()) {
  635
+			ExecutorService executorService = entry.getValue();
  636
+			if (logger.isInfoEnabled()) {
  637
+				logger.info("Stopping executor service " + executorService);
  638
+			}
  639
+			executorServices.add(executorService);
  640
+			if (this.shutdownForced) {
  641
+				executorService.shutdownNow();
  642
+			}
  643
+			else {
  644
+				executorService.shutdown();
  645
+			}
1
Oleg Zhurakousky
olegz added a note May 08, 2012

seems like this block of code is the same in all 3 methods. May be another private method?

executorServices.add(executorService);
            if (this.shutdownForced) {
                executorService.shutdownNow();
            }
            else {
                executorService.shutdown();
            }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...tegration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
((190 lines not shown))
  636
+			if (logger.isInfoEnabled()) {
  637
+				logger.info("Stopping executor service " + executorService);
  638
+			}
  639
+			executorServices.add(executorService);
  640
+			if (this.shutdownForced) {
  641
+				executorService.shutdownNow();
  642
+			}
  643
+			else {
  644
+				executorService.shutdown();
  645
+			}
  646
+		}
  647
+		waitForExecutors(executorServices);
  648
+		logger.debug("Stopped other executors");
  649
+	}
  650
+
  651
+	private void waitForExecutors(List<ExecutorService> executorServices) {
1
Oleg Zhurakousky
olegz added a note May 08, 2012

Can you move this method down to where all privates are?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Oleg Zhurakousky olegz commented on the diff May 08, 2012
...tegration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
((142 lines not shown))
  588
+			else {
  589
+				executorService.shutdown();
  590
+			}
  591
+		}
  592
+		waitForExecutors(executorServices);
  593
+		logger.debug("Stopped schedulers");
  594
+	}
  595
+
  596
+	@ManagedOperation
  597
+	public void stopExecutors() {
  598
+		if (logger.isDebugEnabled()) {
  599
+			logger.debug("Stopping executors" + (this.shutdownForced ? "(force)" : ""));
  600
+		}
  601
+		List<ExecutorService> executorServices = new ArrayList<ExecutorService>();
  602
+		Map<String, ThreadPoolTaskExecutor> executors = this.applicationContext
  603
+				.getBeansOfType(ThreadPoolTaskExecutor.class);
2
Oleg Zhurakousky
olegz added a note May 08, 2012

Do we really only care about ThreadPoolTaskExecutor here? The hierarchy of TaskExecutor is pretty extensive and I am assuming you wanted to specifically stop any Spring TaskExecutor here. Also, however unlikely if the executor is a Proxy than it won't be part of the executors map

Gary Russell Collaborator
garyrussell added a note May 08, 2012

I was really going after and our internal TaskScheduler. I think there are issues going after everything. I'll look again, but we might have to defer to the follow up JIRA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Oleg Zhurakousky olegz commented on the diff May 08, 2012
...tegration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
((116 lines not shown))
  562
+				if (logger.isInfoEnabled()) {
  563
+					logger.info("Stopping channel " + channel);
  564
+				}
  565
+				((Lifecycle) channel).stop();
  566
+			}
  567
+		}
  568
+	}
  569
+
  570
+	@ManagedOperation
  571
+	public void stopSchedulers() {
  572
+		if (logger.isDebugEnabled()) {
  573
+			logger.debug("Stopping schedulers " + (this.shutdownForced ? "(force)" : ""));
  574
+		}
  575
+		List<ExecutorService> executorServices = new ArrayList<ExecutorService>();
  576
+		Map<String, ThreadPoolTaskScheduler> schedulers = this.applicationContext
  577
+				.getBeansOfType(ThreadPoolTaskScheduler.class);
1
Oleg Zhurakousky
olegz added a note May 08, 2012

Similar comment as for TaskExecutor. You probably want all instances of TaskScheduler, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Oleg Zhurakousky olegz commented on the diff May 08, 2012
...tegration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
((98 lines not shown))
  544
+				}
  545
+				((LifecycleMessageSourceMetrics) sourceMetrics).stop();
  546
+			}
  547
+			else {
  548
+				if (logger.isInfoEnabled()) {
  549
+					logger.info("Message source " + sourceMetrics + " cannot be stopped");
  550
+				}
  551
+			}
  552
+		}
  553
+	}
  554
+
  555
+	@ManagedOperation
  556
+	public void stopActiveChannels() {
  557
+		// Stop any "active" channels (JMS etc).
  558
+		for (Entry<String, DirectChannelMetrics> entry : this.allChannelsByName.entrySet()) {
  559
+			DirectChannelMetrics metrics = entry.getValue();
3
Oleg Zhurakousky
olegz added a note May 08, 2012

Same MessageChannelMetrics?

Gary Russell Collaborator
garyrussell added a note May 08, 2012

??

Oleg Zhurakousky
olegz added a note May 08, 2012

Never mind this one

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...tegration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
((210 lines not shown))
  656
+					logger.error("Executor service " + executorService + " failed to terminate");
  657
+				}
  658
+			} catch (InterruptedException e) {
  659
+				Thread.currentThread().interrupt();
  660
+				logger.error("Interrupted while shutting down executor service " + executorService);
  661
+				throw new MessagingException("Interrupted while shutting down", e);
  662
+			}
  663
+			if (System.currentTimeMillis() > this.shutdownDeadline) {
  664
+				logger.error("Timed out before waiting for all executor services");
  665
+			}
  666
+		}
  667
+	}
  668
+
  669
+	@ManagedOperation
  670
+	public void stopOtherActiveComponents() {
  671
+		logger.debug("Stopping OrderlyShutdownCapable components");
2
Oleg Zhurakousky
olegz added a note May 08, 2012

Should this be called stopOrderlyShtdownCapableComponents? My concern is that in line 497 its the first method that is called and other somehow implies that when everything specific is shut down you can now shut down all others. wdyt?

Gary Russell Collaborator
garyrussell added a note May 08, 2012

Yeah - this was originally caled last (see the JIRA comments).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
added some commits April 02, 2012
Gary Russell INT-2485 Orderly Shutdown
Initial commit - @ManagedOperation on IMBE

- can be invoked via JMX, <control-bus/>, or getting a reference to
the IMBE from the application context.

INT-2485 Updates After Review Comments (JIRA)

* Shutdown Schedulers first, and wait for them
* Add a force parameter, which overrides thread pool shutdown options
* Shutdown Sources/Channels after all thread pools have stopped
* Mark other components as OrderlyShutdownCapable (e.g. JMS/AMQP Listener containers) and shut them down first
* Wait for remaining time to allow for quiescence

Also
* remove TimeUnit parameter (not JMX-friendly); time limit is now always milliseconds
* If thread pools don't stop in time limit, force them down.

INT-2485 Handle Self-Destruction

Add shutdown-executor to IMBE.

When the shutdown was called on a Spring-Managed thread, the shutdown
was not clean because we timed out waiting for the current thread to
terminate. After that, we force terminated other components.

Now, by providing a dedicated Executor for the shutdown process, it
is used for the shutdown instead of the current thread. This Executor
is *not* shutdown.

It is not necessary to provide an Executor if the stopActiveComponents()
method is called on some other thread that is not involved in the
shutdown.

Also adds executor name to logs, when available.

INT-2485 Polishing

Fix MBean object name collision when running all tests.

INT-2485 Enable TCP Shutdown

Make TCP connection factories 'OrderlyShutdownCapable' so
they are stopped before schedulers/executors in order for
them to release any executor threads they are holding.
8b370d0
Gary Russell INT-2485 Polishing
Didn't need DirectFieldAccessor - scheduler and executor have
an accessor for the native ExecutorService.

Copyrights
7f37c4f
Gary Russell INT-2485 Polishing
schemaLocation version.
356ef0b
Gary Russell INT-2485 PR Review Polishing cb7b183
Gary Russell
Collaborator

Pushed PR Review Fixes; see last commit

Oleg Zhurakousky
olegz commented on cb7b183 May 10, 2012

Looks good, merging

Oleg Zhurakousky olegz closed this May 10, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 4 unique commits by 1 author.

May 08, 2012
Gary Russell INT-2485 Orderly Shutdown
Initial commit - @ManagedOperation on IMBE

- can be invoked via JMX, <control-bus/>, or getting a reference to
the IMBE from the application context.

INT-2485 Updates After Review Comments (JIRA)

* Shutdown Schedulers first, and wait for them
* Add a force parameter, which overrides thread pool shutdown options
* Shutdown Sources/Channels after all thread pools have stopped
* Mark other components as OrderlyShutdownCapable (e.g. JMS/AMQP Listener containers) and shut them down first
* Wait for remaining time to allow for quiescence

Also
* remove TimeUnit parameter (not JMX-friendly); time limit is now always milliseconds
* If thread pools don't stop in time limit, force them down.

INT-2485 Handle Self-Destruction

Add shutdown-executor to IMBE.

When the shutdown was called on a Spring-Managed thread, the shutdown
was not clean because we timed out waiting for the current thread to
terminate. After that, we force terminated other components.

Now, by providing a dedicated Executor for the shutdown process, it
is used for the shutdown instead of the current thread. This Executor
is *not* shutdown.

It is not necessary to provide an Executor if the stopActiveComponents()
method is called on some other thread that is not involved in the
shutdown.

Also adds executor name to logs, when available.

INT-2485 Polishing

Fix MBean object name collision when running all tests.

INT-2485 Enable TCP Shutdown

Make TCP connection factories 'OrderlyShutdownCapable' so
they are stopped before schedulers/executors in order for
them to release any executor threads they are holding.
8b370d0
Gary Russell INT-2485 Polishing
Didn't need DirectFieldAccessor - scheduler and executor have
an accessor for the native ExecutorService.

Copyrights
7f37c4f
Gary Russell INT-2485 Polishing
schemaLocation version.
356ef0b
May 09, 2012
Gary Russell INT-2485 PR Review Polishing cb7b183
This page is out of date. Refresh to see the latest.
7  ...tegration-amqp/src/main/java/org/springframework/integration/amqp/inbound/AmqpInboundChannelAdapter.java
... ...
@@ -1,5 +1,5 @@
1 1
 /*
2  
- * Copyright 2002-2011 the original author or authors.
  2
+ * Copyright 2002-2012 the original author or authors.
3 3
  *
4 4
  * Licensed under the Apache License, Version 2.0 (the "License");
5 5
  * you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
25 25
 import org.springframework.amqp.support.converter.SimpleMessageConverter;
26 26
 import org.springframework.integration.amqp.support.AmqpHeaderMapper;
27 27
 import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper;
  28
+import org.springframework.integration.core.OrderlyShutdownCapable;
28 29
 import org.springframework.integration.endpoint.MessageProducerSupport;
29 30
 import org.springframework.integration.support.MessageBuilder;
30 31
 import org.springframework.util.Assert;
@@ -34,9 +35,11 @@
34 35
  * Spring Integration Messages, and sends the results to a Message Channel.
35 36
  * 
36 37
  * @author Mark Fisher
  38
+ * @author Gary Russell
37 39
  * @since 2.1
38 40
  */
39  
-public class AmqpInboundChannelAdapter extends MessageProducerSupport {
  41
+public class AmqpInboundChannelAdapter extends MessageProducerSupport implements
  42
+		OrderlyShutdownCapable {
40 43
 
41 44
 	private final AbstractMessageListenerContainer messageListenerContainer;
42 45
 
30  spring-integration-core/src/main/java/org/springframework/integration/core/OrderlyShutdownCapable.java
... ...
@@ -0,0 +1,30 @@
  1
+/*
  2
+ * Copyright 2002-2012 the original author or authors.
  3
+ *
  4
+ * Licensed under the Apache License, Version 2.0 (the "License");
  5
+ * you may not use this file except in compliance with the License.
  6
+ * You may obtain a copy of the License at
  7
+ *
  8
+ *      http://www.apache.org/licenses/LICENSE-2.0
  9
+ *
  10
+ * Unless required by applicable law or agreed to in writing, software
  11
+ * distributed under the License is distributed on an "AS IS" BASIS,
  12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+ * See the License for the specific language governing permissions and
  14
+ * limitations under the License.
  15
+ */
  16
+package org.springframework.integration.core;
  17
+
  18
+/**
  19
+ * Marker interface for components that wish to be considered for
  20
+ * an orderly shutdown using management interfaces. Components that
  21
+ * implement this interface will be stopped before schedulers,
  22
+ * executors etc, in order for them to free up any execution
  23
+ * threads they may be holding.
  24
+ * @author Gary Russell
  25
+ * @since 2.2
  26
+ *
  27
+ */
  28
+public interface OrderlyShutdownCapable {
  29
+
  30
+}
3  ...ration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractConnectionFactory.java
@@ -40,6 +40,7 @@
40 40
 import org.springframework.core.serializer.Serializer;
41 41
 import org.springframework.integration.MessagingException;
42 42
 import org.springframework.integration.context.IntegrationObjectSupport;
  43
+import org.springframework.integration.core.OrderlyShutdownCapable;
43 44
 import org.springframework.integration.ip.tcp.serializer.ByteArrayCrLfSerializer;
44 45
 import org.springframework.util.Assert;
45 46
 
@@ -51,7 +52,7 @@
51 52
  *
52 53
  */
53 54
 public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
54  
-		implements ConnectionFactory, Runnable, SmartLifecycle {
  55
+		implements ConnectionFactory, Runnable, SmartLifecycle, OrderlyShutdownCapable {
55 56
 
56 57
 	protected static final int DEFAULT_REPLY_TIMEOUT = 10000;
57 58
 
7  spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsMessageDrivenEndpoint.java
... ...
@@ -1,5 +1,5 @@
1 1
 /*
2  
- * Copyright 2002-2011 the original author or authors.
  2
+ * Copyright 2002-2012 the original author or authors.
3 3
  *
4 4
  * Licensed under the Apache License, Version 2.0 (the "License");
5 5
  * you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
17 17
 package org.springframework.integration.jms;
18 18
 
19 19
 import org.springframework.beans.factory.DisposableBean;
  20
+import org.springframework.integration.core.OrderlyShutdownCapable;
20 21
 import org.springframework.integration.endpoint.AbstractEndpoint;
21 22
 import org.springframework.jms.listener.AbstractMessageListenerContainer;
22 23
 import org.springframework.util.Assert;
@@ -27,8 +28,10 @@
27 28
  * 
28 29
  * @author Mark Fisher
29 30
  * @author Oleg Zhurakousky
  31
+ * @author Gary Russell
30 32
  */
31  
-public class JmsMessageDrivenEndpoint extends AbstractEndpoint implements DisposableBean {
  33
+public class JmsMessageDrivenEndpoint extends AbstractEndpoint implements
  34
+		DisposableBean, OrderlyShutdownCapable {
32 35
 
33 36
 	private final AbstractMessageListenerContainer listenerContainer;
34 37
 
3  spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterParser.java
... ...
@@ -1,5 +1,5 @@
1 1
 /*
2  
- * Copyright 2002-2010 the original author or authors.
  2
+ * Copyright 2002-2012 the original author or authors.
3 3
  * 
4 4
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5 5
  * the License. You may obtain a copy of the License at
@@ -60,6 +60,7 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit
60 60
 		IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "default-domain");
61 61
 		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "object-name-static-properties");
62 62
 		IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "managed-components", "componentNamePatterns");
  63
+		IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "shutdown-executor");
63 64
 		
64 65
 		builder.addPropertyValue("server", mbeanServer);
65 66
 		this.registerMBeanExporterHelper(parserContext.getRegistry());
287  spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
... ...
@@ -1,5 +1,5 @@
1 1
 /*
2  
- * Copyright 2002-2010 the original author or authors.
  2
+ * Copyright 2002-2012 the original author or authors.
3 3
  * 
4 4
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5 5
  * the License. You may obtain a copy of the License at
@@ -14,11 +14,18 @@
14 14
 package org.springframework.integration.monitor;
15 15
 
16 16
 import java.lang.reflect.Field;
  17
+import java.util.ArrayList;
17 18
 import java.util.HashMap;
18 19
 import java.util.HashSet;
  20
+import java.util.List;
19 21
 import java.util.Map;
  22
+import java.util.Map.Entry;
20 23
 import java.util.Properties;
21 24
 import java.util.Set;
  25
+import java.util.concurrent.Executor;
  26
+import java.util.concurrent.ExecutorService;
  27
+import java.util.concurrent.TimeUnit;
  28
+import java.util.concurrent.atomic.AtomicBoolean;
22 29
 import java.util.concurrent.atomic.AtomicLong;
23 30
 import java.util.concurrent.locks.ReentrantLock;
24 31
 
@@ -44,12 +51,17 @@
44 51
 import org.springframework.beans.factory.BeanFactoryAware;
45 52
 import org.springframework.beans.factory.ListableBeanFactory;
46 53
 import org.springframework.beans.factory.config.BeanPostProcessor;
  54
+import org.springframework.context.ApplicationContext;
  55
+import org.springframework.context.ApplicationContextAware;
47 56
 import org.springframework.context.Lifecycle;
48 57
 import org.springframework.context.SmartLifecycle;
  58
+import org.springframework.core.task.support.ExecutorServiceAdapter;
49 59
 import org.springframework.integration.MessageChannel;
  60
+import org.springframework.integration.MessagingException;
50 61
 import org.springframework.integration.channel.QueueChannel;
51 62
 import org.springframework.integration.core.MessageHandler;
52 63
 import org.springframework.integration.core.MessageSource;
  64
+import org.springframework.integration.core.OrderlyShutdownCapable;
53 65
 import org.springframework.integration.core.PollableChannel;
54 66
 import org.springframework.integration.endpoint.AbstractEndpoint;
55 67
 import org.springframework.jmx.export.MBeanExporter;
@@ -62,6 +74,8 @@
62 74
 import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler;
63 75
 import org.springframework.jmx.export.naming.MetadataNamingStrategy;
64 76
 import org.springframework.jmx.support.MetricType;
  77
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  78
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
65 79
 import org.springframework.util.Assert;
66 80
 import org.springframework.util.PatternMatchUtils;
67 81
 import org.springframework.util.ReflectionUtils;
@@ -94,7 +108,7 @@
94 108
  */
95 109
 @ManagedResource
96 110
 public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostProcessor, BeanFactoryAware,
97  
-		BeanClassLoaderAware, SmartLifecycle {
  111
+		ApplicationContextAware, BeanClassLoaderAware, SmartLifecycle, Runnable {
98 112
 
99 113
 	private static final Log logger = LogFactory.getLog(IntegrationMBeanExporter.class);
100 114
 
@@ -104,6 +118,8 @@
104 118
 
105 119
 	private ListableBeanFactory beanFactory;
106 120
 
  121
+	private ApplicationContext applicationContext;
  122
+
107 123
 	private Map<Object, AtomicLong> anonymousHandlerCounters = new HashMap<Object, AtomicLong>();
108 124
 
109 125
 	private Map<Object, AtomicLong> anonymousSourceCounters = new HashMap<Object, AtomicLong>();
@@ -122,6 +138,12 @@
122 138
 
123 139
 	private Map<String, MessageSourceMetrics> sourcesByName = new HashMap<String, MessageSourceMetrics>();
124 140
 
  141
+	private Map<String, DirectChannelMetrics> allChannelsByName = new HashMap<String, DirectChannelMetrics>();
  142
+
  143
+	private Map<String, MessageHandlerMetrics> allHandlersByName = new HashMap<String, MessageHandlerMetrics>();
  144
+
  145
+	private Map<String, MessageSourceMetrics> allSourcesByName = new HashMap<String, MessageSourceMetrics>();
  146
+
125 147
 	private Map<String, String> beansByEndpointName = new HashMap<String, String>();
126 148
 
127 149
 	private ClassLoader beanClassLoader;
@@ -146,6 +168,14 @@
146 168
 
147 169
 	private String[] componentNamePatterns = { "*" };
148 170
 
  171
+	private volatile Executor shutdownExecutor;
  172
+
  173
+	private volatile long shutdownDeadline;
  174
+
  175
+	private volatile boolean shutdownForced;
  176
+
  177
+	private final AtomicBoolean shuttingDown = new AtomicBoolean();
  178
+
149 179
 	public IntegrationMBeanExporter() {
150 180
 		super();
151 181
 		// Shouldn't be necessary, but to be on the safe side...
@@ -191,6 +221,17 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
191 221
 		this.beanFactory = (ListableBeanFactory) beanFactory;
192 222
 	}
193 223
 
  224
+	public void setApplicationContext(ApplicationContext applicationContext)
  225
+			throws BeansException {
  226
+		Assert.notNull(applicationContext, "ApplicationContext may not be null");
  227
+		this.applicationContext = applicationContext;
  228
+	}
  229
+
  230
+	public void setShutdownExecutor(Executor shutdownExecutor) {
  231
+		Assert.notNull(shutdownExecutor, "Shutdown Executor may not be null");
  232
+		this.shutdownExecutor = shutdownExecutor;
  233
+	}
  234
+
194 235
 	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
195 236
 
196 237
 		if (bean instanceof Advised) {
@@ -409,6 +450,217 @@ public void destroy() {
409 450
 		}
410 451
 	}
411 452
 
  453
+	/**
  454
+	 * Shutdown active components. If the thread calling this method is
  455
+	 * managed by a Spring-managed executor, you should provide a specific
  456
+	 * dedicated executor via the {@link #setShutdownExecutor(Executor))}
  457
+	 * method. When this is provided, the shutdown will be performed on one
  458
+	 * of its threads, instead of the calling thread; thus avoiding
  459
+	 * the situation where we will wait for the current thread to terminate.
  460
+	 * <p> It is not necessary to supply this executor service if the
  461
+	 * current thread will not, itself, be shutdown as a result of
  462
+	 * calling this method.
  463
+	 * <p><b>Note:</b> The supplied executor service
  464
+	 * will <b>not</b> be shut down.
  465
+	 *
  466
+	 * @param force If true, stop the executors with shutdownNow(), canceling
  467
+	 * running tasks. Overrides any settings on schedulers/executors. When true
  468
+	 * may result in error messages being sent to error channels.
  469
+	 * @param howLong The time to wait in total for all activities to complete
  470
+	 * in milliseconds.
  471
+	 */
  472
+	@ManagedOperation
  473
+	public void stopActiveComponents(boolean force, long howLong) {
  474
+		if (!this.shuttingDown.compareAndSet(false, true)) {
  475
+			logger.error("Shutdown already in process");
  476
+			return;
  477
+		}
  478
+		this.shutdownDeadline = System.currentTimeMillis() + howLong;
  479
+		this.shutdownForced = force;
  480
+		if (this.shutdownExecutor == null) {
  481
+			try {
  482
+				logger.debug("Running shutdown on current thread");
  483
+				this.run();
  484
+			} catch (Exception e) {
  485
+				logger.error("Orderly shutdown failed", e);
  486
+			}
  487
+		}
  488
+		else {
  489
+			logger.debug("Launching shutdown on another thread");
  490
+			this.shutdownExecutor.execute(this);
  491
+		}
  492
+	}
  493
+
  494
+	/**
  495
+	 * Perform orderly shutdown - called or executed from
  496
+	 * {@link #stopActiveComponents(boolean, long)}.
  497
+	 */
  498
+	public void run() {
  499
+		try {
  500
+			this.stopOrderlyShutdownCapableComponents();
  501
+			this.stopActiveChannels();
  502
+			this.stopSchedulers();
  503
+			if (System.currentTimeMillis() > this.shutdownDeadline) {
  504
+				logger.error("Timed out before waiting for all schedulers to complete");
  505
+			}
  506
+			this.stopExecutors();
  507
+			if (System.currentTimeMillis() > this.shutdownDeadline) {
  508
+				logger.error("Timed out before waiting for all executors to complete");
  509
+			}
  510
+			this.stopNonSpringExecutors();
  511
+			if (System.currentTimeMillis() > this.shutdownDeadline) {
  512
+				logger.error("Timed out before waiting for all non-Spring executors to complete");
  513
+			}
  514
+			this.stopMessageSources();
  515
+			// Wait any remaining time for messages to quiesce
  516
+			long timeLeft = this.shutdownDeadline - System.currentTimeMillis();
  517
+			if (timeLeft > 0) {
  518
+				try {
  519
+					Thread.sleep(timeLeft);
  520
+				} catch (InterruptedException e) {
  521
+					Thread.currentThread().interrupt();
  522
+					logger.error("Interrupted while waiting for quiesce");
  523
+				}
  524
+			}
  525
+			else {
  526
+				this.shutdownForced = true;
  527
+				this.stopSchedulers();
  528
+				this.stopExecutors();
  529
+				this.stopNonSpringExecutors();
  530
+			}
  531
+		}
  532
+		finally {
  533
+			this.shuttingDown.set(false);
  534
+		}
  535
+	}
  536
+
  537
+	/**
  538
+	 * Stops all message sources - may cause interrupts.
  539
+	 */
  540
+	@ManagedOperation
  541
+	public void stopMessageSources() {
  542
+		for (Entry<String, MessageSourceMetrics> entry : this.allSourcesByName.entrySet()) {
  543
+			MessageSourceMetrics sourceMetrics = entry.getValue();
  544
+			if (sourceMetrics instanceof LifecycleMessageSourceMetrics) {
  545
+				if (logger.isInfoEnabled()) {
  546
+					logger.info("Stopping message source " + sourceMetrics);
  547
+				}
  548
+				((LifecycleMessageSourceMetrics) sourceMetrics).stop();
  549
+			}
  550
+			else {
  551
+				if (logger.isInfoEnabled()) {
  552
+					logger.info("Message source " + sourceMetrics + " cannot be stopped");
  553
+				}
  554
+			}
  555
+		}
  556
+	}
  557
+
  558
+	@ManagedOperation
  559
+	public void stopActiveChannels() {
  560
+		// Stop any "active" channels (JMS etc).
  561
+		for (Entry<String, DirectChannelMetrics> entry : this.allChannelsByName.entrySet()) {
  562
+			DirectChannelMetrics metrics = entry.getValue();
  563
+			MessageChannel channel = metrics.getMessageChannel();
  564
+			if (channel instanceof Lifecycle) {
  565
+				if (logger.isInfoEnabled()) {
  566
+					logger.info("Stopping channel " + channel);
  567
+				}
  568
+				((Lifecycle) channel).stop();
  569
+			}
  570
+		}
  571
+	}
  572
+
  573
+	@ManagedOperation
  574
+	public void stopSchedulers() {
  575
+		if (logger.isDebugEnabled()) {
  576
+			logger.debug("Stopping schedulers " + (this.shutdownForced ? "(force)" : ""));
  577
+		}
  578
+		List<ExecutorService> executorServices = new ArrayList<ExecutorService>();
  579
+		Map<String, ThreadPoolTaskScheduler> schedulers = this.applicationContext
  580
+				.getBeansOfType(ThreadPoolTaskScheduler.class);
  581
+		for (Entry<String, ThreadPoolTaskScheduler> entry : schedulers.entrySet()) {
  582
+			ThreadPoolTaskScheduler scheduler = entry.getValue();
  583
+			if (logger.isInfoEnabled()) {
  584
+				logger.info("Stopping scheduler " + scheduler.getThreadNamePrefix());
  585
+			}
  586
+			ExecutorService executorService = scheduler.getScheduledExecutor();
  587
+			executorServices.add(executorService);
  588
+			doShutdownExecutorService(executorService);
  589
+		}
  590
+		waitForExecutors(executorServices);
  591
+		logger.debug("Stopped schedulers");
  592
+	}
  593
+
  594
+	@ManagedOperation
  595
+	public void stopExecutors() {
  596
+		if (logger.isDebugEnabled()) {
  597
+			logger.debug("Stopping executors" + (this.shutdownForced ? "(force)" : ""));
  598
+		}
  599
+		List<ExecutorService> executorServices = new ArrayList<ExecutorService>();
  600
+		Map<String, ThreadPoolTaskExecutor> executors = this.applicationContext
  601
+				.getBeansOfType(ThreadPoolTaskExecutor.class);
  602
+		for (Entry<String, ThreadPoolTaskExecutor> entry : executors.entrySet()) {
  603
+			ThreadPoolTaskExecutor executor = entry.getValue();
  604
+			if (executor == this.shutdownExecutor) {
  605
+				logger.debug("Skipping shutdown of shutdown executor");
  606
+			}
  607
+			else {
  608
+				if (logger.isInfoEnabled()) {
  609
+					logger.info("Stopping executor " + executor.getThreadNamePrefix());
  610
+				}
  611
+				ExecutorService executorService = executor.getThreadPoolExecutor();
  612
+				executorServices.add(executorService);
  613
+				doShutdownExecutorService(executorService);
  614
+			}
  615
+		}
  616
+		waitForExecutors(executorServices);
  617
+		logger.debug("Stopped executors");
  618
+	}
  619
+
  620
+	@ManagedOperation
  621
+	public void stopNonSpringExecutors() {
  622
+		if (logger.isDebugEnabled()) {
  623
+			logger.debug("Stopping other executors" + (this.shutdownForced ? "(force)" : ""));
  624
+		}
  625
+		List<ExecutorService> executorServices = new ArrayList<ExecutorService>();
  626
+		Map<String, ExecutorService> nonSpringExecutors = this.applicationContext
  627
+				.getBeansOfType(ExecutorService.class);
  628
+		for (Entry<String, ExecutorService> entry : nonSpringExecutors.entrySet()) {
  629
+			ExecutorService executorService = entry.getValue();
  630
+			if (!(executorService instanceof ExecutorServiceAdapter)) {
  631
+				if (logger.isInfoEnabled()) {
  632
+					logger.info("Stopping executor service " + executorService);
  633
+				}
  634
+				executorServices.add(executorService);
  635
+				doShutdownExecutorService(executorService);
  636
+			}
  637
+			else {
  638
+				if (logger.isDebugEnabled()) {
  639
+					logger.debug("Ignoring ExecutorServiceAdapter");
  640
+				}
  641
+			}
  642
+		}
  643
+		waitForExecutors(executorServices);
  644
+		logger.debug("Stopped other executors");
  645
+	}
  646
+
  647
+	@ManagedOperation
  648
+	public void stopOrderlyShutdownCapableComponents() {
  649
+		logger.debug("Stopping OrderlyShutdownCapable components");
  650
+		Map<String, OrderlyShutdownCapable> candidates = this.applicationContext
  651
+				.getBeansOfType(OrderlyShutdownCapable.class);
  652
+		for (Entry<String, OrderlyShutdownCapable> candidateEntry : candidates.entrySet()) {
  653
+			OrderlyShutdownCapable candidate = candidateEntry.getValue();
  654
+			if (candidate instanceof Lifecycle) {
  655
+				if (logger.isInfoEnabled()) {
  656
+					logger.info("Stopping component " + candidate);
  657
+				}
  658
+				((Lifecycle) candidate).stop();
  659
+			}
  660
+		}
  661
+		logger.debug("Stopped OrderlyShutdownCapable components");
  662
+	}
  663
+
412 664
 	@ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Channel Count")
413 665
 	public int getChannelCount() {
414 666
 		return channelsByName.size();
@@ -500,9 +752,37 @@ protected void registerBeans() {
500 752
 		}
501 753
 	}
502 754
 
  755
+	private void doShutdownExecutorService(ExecutorService executorService) {
  756
+		if (this.shutdownForced) {
  757
+			executorService.shutdownNow();
  758
+		}
  759
+		else {
  760
+			executorService.shutdown();
  761
+		}
  762
+	}
  763
+
  764
+	private void waitForExecutors(List<ExecutorService> executorServices) {
  765
+		for (ExecutorService executorService : executorServices) {
  766
+			try {
  767
+				if (!executorService.awaitTermination(this.shutdownDeadline
  768
+						- System.currentTimeMillis(), TimeUnit.MILLISECONDS)) {
  769
+					logger.error("Executor service " + executorService + " failed to terminate");
  770
+				}
  771
+			} catch (InterruptedException e) {
  772
+				Thread.currentThread().interrupt();
  773
+				logger.error("Interrupted while shutting down executor service " + executorService);
  774
+				throw new MessagingException("Interrupted while shutting down", e);
  775
+			}
  776
+			if (System.currentTimeMillis() > this.shutdownDeadline) {
  777
+				logger.error("Timed out before waiting for all executor services");
  778
+			}
  779
+		}
  780
+	}
  781
+
503 782
 	private void registerChannels() {
504 783
 		for (DirectChannelMetrics monitor : channels) {
505 784
 			String name = monitor.getName();
  785
+			this.allChannelsByName.put(name, monitor);
506 786
 			if (!PatternMatchUtils.simpleMatch(this.componentNamePatterns, name)) {
507 787
 				continue;
508 788
 			}
@@ -528,6 +808,7 @@ private void registerHandlers() {
528 808
 		for (SimpleMessageHandlerMetrics source : handlers) {
529 809
 			MessageHandlerMetrics monitor = enhanceHandlerMonitor(source);
530 810
 			String name = monitor.getName();
  811
+			this.allHandlersByName.put(name, monitor);
531 812
 			if (!PatternMatchUtils.simpleMatch(this.componentNamePatterns, name)) {
532 813
 				continue;
533 814
 			}
@@ -552,6 +833,7 @@ private void registerSources() {
552 833
 		for (SimpleMessageSourceMetrics source : sources) {
553 834
 			MessageSourceMetrics monitor = enhanceSourceMonitor(source);
554 835
 			String name = monitor.getName();
  836
+			this.allSourcesByName.put(name, monitor);
555 837
 			if (!PatternMatchUtils.simpleMatch(this.componentNamePatterns, name)) {
556 838
 				continue;
557 839
 			}
@@ -901,5 +1183,4 @@ private static Object getField(Object target, String name) {
901 1183
 		ReflectionUtils.makeAccessible(field);
902 1184
 		return ReflectionUtils.getField(field, target);
903 1185
 	}
904  
-
905 1186
 }
3  spring-integration-jmx/src/main/resources/META-INF/spring.schemas
... ...
@@ -1,3 +1,4 @@
1 1
 http\://www.springframework.org/schema/integration/jmx/spring-integration-jmx-2.0.xsd=org/springframework/integration/jmx/config/spring-integration-jmx-2.0.xsd
2 2
 http\://www.springframework.org/schema/integration/jmx/spring-integration-jmx-2.1.xsd=org/springframework/integration/jmx/config/spring-integration-jmx-2.1.xsd
3  
-http\://www.springframework.org/schema/integration/jmx/spring-integration-jmx.xsd=org/springframework/integration/jmx/config/spring-integration-jmx-2.1.xsd
  3
+http\://www.springframework.org/schema/integration/jmx/spring-integration-jmx-2.2.xsd=org/springframework/integration/jmx/config/spring-integration-jmx-2.2.xsd
  4
+http\://www.springframework.org/schema/integration/jmx/spring-integration-jmx.xsd=org/springframework/integration/jmx/config/spring-integration-jmx-2.2.xsd
202  ...gration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-2.2.xsd
... ...
@@ -0,0 +1,202 @@
  1
+<?xml version="1.0" encoding="UTF-8"?>
  2
+<xsd:schema xmlns="http://www.springframework.org/schema/integration/jmx" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  3
+	xmlns:beans="http://www.springframework.org/schema/beans" xmlns:tool="http://www.springframework.org/schema/tool"
  4
+	xmlns:integration="http://www.springframework.org/schema/integration" targetNamespace="http://www.springframework.org/schema/integration/jmx"
  5
+	elementFormDefault="qualified" attributeFormDefault="unqualified">
  6
+
  7
+	<xsd:import namespace="http://www.springframework.org/schema/beans" />
  8
+	<xsd:import namespace="http://www.springframework.org/schema/tool" />
  9
+	<xsd:import namespace="http://www.springframework.org/schema/integration" schemaLocation="http://www.springframework.org/schema/integration/spring-integration-2.2.xsd" />
  10
+
  11
+	<xsd:annotation>
  12
+		<xsd:documentation><![CDATA[
  13
+	Defines the configuration elements for Spring Integration's JMX adapters.
  14
+		]]></xsd:documentation>
  15
+	</xsd:annotation>
  16
+
  17
+	<xsd:element name="attribute-polling-channel-adapter">
  18
+		<xsd:annotation>
  19
+			<xsd:documentation>
  20
+				Defines an inbound Channel Adapter that polls for JMX attribute values.
  21
+			</xsd:documentation>
  22
+		</xsd:annotation>
  23
+		<xsd:complexType>
  24
+			<xsd:complexContent>
  25
+				<xsd:extension base="adapterType">
  26
+					<xsd:sequence minOccurs="0" maxOccurs="1">
  27
+						<xsd:element ref="integration:poller" />
  28
+					</xsd:sequence>
  29
+					<xsd:attribute name="attribute-name" type="xsd:string" use="required" />
  30
+					<xsd:attribute name="auto-startup" type="xsd:string" default="true" />
  31
+				</xsd:extension>
  32
+			</xsd:complexContent>
  33
+		</xsd:complexType>
  34
+	</xsd:element>
  35
+	<xsd:element name="operation-invoking-outbound-gateway">
  36
+		<xsd:annotation>
  37
+			<xsd:documentation>
  38
+				Defines an outbound Gateway which allows for Message-driven invocation of managed operations that
  39
+				return values
  40
+			</xsd:documentation>
  41
+		</xsd:annotation>
  42
+		<xsd:complexType>
  43
+			<xsd:complexContent>
  44
+				<xsd:extension base="operationInvokingType">
  45
+					<xsd:attribute name="request-channel" type="xsd:string" use="required" />
  46
+					<xsd:attribute name="reply-channel" type="xsd:string" use="optional" />
  47
+				</xsd:extension>
  48
+			</xsd:complexContent>
  49
+		</xsd:complexType>
  50
+	</xsd:element>
  51
+
  52
+	<xsd:element name="operation-invoking-channel-adapter">
  53
+		<xsd:annotation>
  54
+			<xsd:documentation>
  55
+				Defines an outbound Channel Adapter for invoking JMX operations.
  56
+			</xsd:documentation>
  57
+		</xsd:annotation>
  58
+		<xsd:complexType>
  59
+			<xsd:complexContent>
  60
+				<xsd:extension base="operationInvokingType">
  61
+					<xsd:attribute name="channel" type="xsd:string" use="optional" />
  62
+				</xsd:extension>
  63
+			</xsd:complexContent>
  64
+		</xsd:complexType>
  65
+	</xsd:element>
  66
+
  67
+	<xsd:element name="notification-listening-channel-adapter">
  68
+		<xsd:annotation>
  69
+			<xsd:documentation>
  70
+				Defines an inbound Channel Adapter that listens for JMX notifications.
  71
+			</xsd:documentation>
  72
+		</xsd:annotation>
  73
+		<xsd:complexType>
  74
+			<xsd:complexContent>
  75
+				<xsd:extension base="adapterType">
  76
+					<xsd:attribute name="notification-filter" type="xsd:string" use="optional" />
  77
+					<xsd:attribute name="handback" type="xsd:string" use="optional" />
  78
+					<xsd:attribute name="send-timeout" type="xsd:string" use="optional" />
  79
+				</xsd:extension>
  80
+			</xsd:complexContent>
  81
+		</xsd:complexType>
  82
+	</xsd:element>
  83
+
  84
+	<xsd:element name="notification-publishing-channel-adapter">
  85
+		<xsd:annotation>
  86
+			<xsd:documentation>
  87
+				Defines an outbound Channel Adapter that publishes JMX notifications.
  88
+			</xsd:documentation>
  89
+		</xsd:annotation>
  90
+		<xsd:complexType>
  91
+			<xsd:complexContent>
  92
+				<xsd:extension base="adapterType">
  93
+					<xsd:attribute name="default-notification-type" type="xsd:string" use="optional" />
  94
+				</xsd:extension>
  95
+			</xsd:complexContent>
  96
+		</xsd:complexType>
  97
+	</xsd:element>
  98
+
  99
+	<xsd:element name="mbean-export">
  100
+		<xsd:annotation>
  101
+			<xsd:documentation>
  102
+				Exports Message Channels and Endpoints as MBeans.
  103
+			</xsd:documentation>
  104
+		</xsd:annotation>
  105
+		<xsd:complexType>
  106
+			<xsd:complexContent>
  107
+				<xsd:extension base="mbeanServerIdentifyerType">
  108
+					<xsd:attribute name="default-domain" use="optional">
  109
+						<xsd:annotation>
  110
+							<xsd:documentation>
  111
+								The domain name for the MBeans exported by this Exporter.
  112
+							</xsd:documentation>
  113
+						</xsd:annotation>
  114
+					</xsd:attribute>
  115
+					<xsd:attribute name="object-name-static-properties" use="optional">
  116
+						<xsd:annotation>
  117
+							<xsd:appinfo>
  118
+								<tool:annotation kind="ref">
  119
+									<tool:expected-type type="java.util.Properties" />
  120
+								</tool:annotation>
  121
+							</xsd:appinfo>
  122
+							<xsd:documentation>
  123
+								Static object properties to be used for this domain.  These properties are appended to
  124
+								the ObjectName of all MBeans registered by this component.
  125
+							</xsd:documentation>
  126
+						</xsd:annotation>
  127
+					</xsd:attribute>
  128
+					<xsd:attribute name="managed-components" use="optional">
  129
+						<xsd:annotation>
  130
+							<xsd:documentation>
  131
+								Comma separated list of simple patterns for component names to register (defaults to '*').
  132
+								The pattern is applied to all components before they are registered, looking for a match on
  133
+								the 'name' property of the ObjectName.  A MessageChannel and a MessageHandler (for instance)
  134
+								can share a name because they have a different type, so in that case they would either both
  135
+								be included or both excluded.
  136
+							</xsd:documentation>
  137
+						</xsd:annotation>
  138
+					</xsd:attribute>
  139
+					<xsd:attribute name="shutdown-executor" use="optional">
  140
+						<xsd:annotation>
  141
+							<xsd:appinfo>
  142
+								<tool:annotation kind="ref">
  143
+									<tool:expected-type type="java.util.concurrent.Executor" />
  144
+								</tool:annotation>
  145
+							</xsd:appinfo>
  146
+							<xsd:documentation>
  147
+								An Executor used when shutting down the application using the 'stopActiveComponents()'
  148
+								method. Only required when invoking the operation on a Spring-managed thread, such as
  149
+								via a <control-bus/> from, say, an error flow. Using this executor avoids the
  150
+								problem where the shutdown will wait for the current thread to terminate, time out,