INT-3286: Add @EnableMessageHistory
JIRA: https://jira.springsource.org/browse/INT-3286 Introduce `@EnableMessageHistory` and `MessageHistoryRegistrar`. Refactoring for `MessageHistoryParser` to use `MessageHistoryRegistrar` to follow with DRY Add test for `@EnableMessageHistory` INT-3286: Polishing INT-3286: Enable several MHs with the same value Previously the Framework allowed only one `<message-history>` independent of their `tracked-components`. With introduction of `@EnableMessageHistory` and `MessageHistoryRegistrar` the `MessageHistoryConfigurer` is improved to allow several `<message-history>` or `@EnableMessageHistory` with the same set of `componentNamePatterns`. INT-3286 Polishing + JMX + Docs * Allows setComponentNamePatterns and/or setComponentNamePatternsSet to be used as long as the settings are consistent. * Handle the case where the `MHC` was configured as a bean (no managed set exists) * Add support for changing the component name patterns over JMX + test case * Docs INT-3286 More Polishing * Change bean name to `messageHistoryConfigurer` * Move constant to `ICU` * Export the MBean by the `IMBE`
This commit is contained in:
committed by
Gary Russell
parent
d8e4818c5e
commit
2fd27e3a6c
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2014 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.integration.config;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* Enables {@link org.springframework.integration.history.MessageHistory} for Integration components.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 4.0
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import(MessageHistoryRegistrar.class)
|
||||
public @interface EnableMessageHistory {
|
||||
|
||||
String[] value() default "*";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2014 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.integration.config;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.PropertyValue;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.ManagedSet;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.integration.history.MessageHistoryConfigurer;
|
||||
|
||||
/**
|
||||
* Registers the {@link MessageHistoryConfigurer} {@link org.springframework.beans.factory.config.BeanDefinition}
|
||||
* for {@link org.springframework.integration.history.MessageHistory}.
|
||||
* This registrar is applied from {@code @EnableMessageHistory} on the {@code Configuration} class
|
||||
* or from {@code MessageHistoryParser}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 4.0
|
||||
*/
|
||||
public class MessageHistoryRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
Map<String,Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableMessageHistory.class.getName());
|
||||
Object componentNamePatterns = annotationAttributes.get("value");
|
||||
|
||||
if (componentNamePatterns instanceof String[]) {
|
||||
StringBuilder componentNamePatternsString = new StringBuilder();
|
||||
for (String s : (String[]) componentNamePatterns) {
|
||||
componentNamePatternsString.append(s).append(",");
|
||||
}
|
||||
componentNamePatterns = componentNamePatternsString.substring(0, componentNamePatternsString.length() - 1);
|
||||
}
|
||||
|
||||
if (!registry.containsBeanDefinition(IntegrationContextUtils.INTEGRATION_MESSAGE_HISTORY_CONFIGURER)) {
|
||||
Set<Object> componentNamePatternsSet = new ManagedSet<Object>();
|
||||
componentNamePatternsSet.add(componentNamePatterns);
|
||||
|
||||
AbstractBeanDefinition messageHistoryConfigurer = BeanDefinitionBuilder.genericBeanDefinition(MessageHistoryConfigurer.class)
|
||||
.addPropertyValue("componentNamePatternsSet", componentNamePatternsSet)
|
||||
.getBeanDefinition();
|
||||
|
||||
registry.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_MESSAGE_HISTORY_CONFIGURER, messageHistoryConfigurer);
|
||||
|
||||
}
|
||||
else {
|
||||
BeanDefinition beanDefinition = registry.getBeanDefinition(IntegrationContextUtils.INTEGRATION_MESSAGE_HISTORY_CONFIGURER);
|
||||
PropertyValue propertyValue = beanDefinition
|
||||
.getPropertyValues().getPropertyValue("componentNamePatternsSet");
|
||||
if (propertyValue != null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<Object> currentComponentNamePatternsSet = (Set<Object>) propertyValue.getValue();
|
||||
currentComponentNamePatternsSet.add(componentNamePatterns);
|
||||
}
|
||||
else {
|
||||
Set<Object> componentNamePatternsSet = new ManagedSet<Object>();
|
||||
componentNamePatternsSet.add(componentNamePatterns);
|
||||
beanDefinition.getPropertyValues().addPropertyValue("componentNamePatternsSet", componentNamePatternsSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,47 +16,41 @@
|
||||
|
||||
package org.springframework.integration.config.xml;
|
||||
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.core.type.StandardAnnotationMetadata;
|
||||
import org.springframework.integration.config.MessageHistoryRegistrar;
|
||||
|
||||
/**
|
||||
* The {@code <message-history/>} parser.
|
||||
* Delegates the {@link BeanDefinition} registration to the {@link MessageHistoryRegistrar}.
|
||||
*
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class MessageHistoryParser extends AbstractSimpleBeanDefinitionParser {
|
||||
|
||||
private static final String CONFIGURER_CLASSNAME = "org.springframework.integration.history.MessageHistoryConfigurer";
|
||||
public class MessageHistoryParser implements BeanDefinitionParser {
|
||||
|
||||
private final MessageHistoryRegistrar messageHistoryRegistrar = new MessageHistoryRegistrar();
|
||||
|
||||
@Override
|
||||
protected String getBeanClassName(Element element) {
|
||||
return CONFIGURER_CLASSNAME;
|
||||
}
|
||||
public BeanDefinition parse(final Element element, ParserContext parserContext) {
|
||||
this.messageHistoryRegistrar.registerBeanDefinitions(new StandardAnnotationMetadata(MessageHistoryParser.class) {
|
||||
|
||||
@Override
|
||||
protected boolean shouldGenerateId() {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public Map<String, Object> getAnnotationAttributes(String annotationType) {
|
||||
return Collections.<String, Object>singletonMap("value", element.getAttribute("tracked-components"));
|
||||
}
|
||||
|
||||
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) {
|
||||
if (parserContext.getRegistry().containsBeanDefinition(CONFIGURER_CLASSNAME)) {
|
||||
throw new BeanDefinitionStoreException("At most one MessageHistoryConfigurer may be registered within a context.");
|
||||
}
|
||||
return CONFIGURER_CLASSNAME;
|
||||
}, parserContext.getRegistry());
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void postProcess(BeanDefinitionBuilder beanDefinition, Element element) {
|
||||
|
||||
}
|
||||
|
||||
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "tracked-components", "componentNamePatterns");
|
||||
postProcess(builder, element);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ public abstract class IntegrationContextUtils {
|
||||
|
||||
public static final String INTEGRATION_CONFIGURATION_POST_PROCESSOR_BEAN_NAME = "IntegrationConfigurationBeanFactoryPostProcessor";
|
||||
|
||||
public static final String INTEGRATION_MESSAGE_HISTORY_CONFIGURER = "messageHistoryConfigurer";
|
||||
|
||||
/**
|
||||
* @param beanFactory BeanFactory for lookup, must not be null.
|
||||
* @return The {@link MetadataStore} bean whose name is "metadataStore".
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.integration.history;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -28,20 +29,31 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
|
||||
import org.springframework.context.SmartLifecycle;
|
||||
import org.springframework.jmx.export.annotation.ManagedAttribute;
|
||||
import org.springframework.jmx.export.annotation.ManagedOperation;
|
||||
import org.springframework.jmx.export.annotation.ManagedResource;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
@ManagedResource
|
||||
public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAware {
|
||||
|
||||
private final Log logger = LogFactory.getLog(this.getClass());
|
||||
|
||||
private volatile String[] componentNamePatterns = new String[] { "*" };
|
||||
|
||||
private volatile boolean componentNamePatternsExplicitlySet;
|
||||
|
||||
private final Set<String> currentlyTrackedComponentNames = new HashSet<String>();
|
||||
|
||||
private volatile BeanFactory beanFactory;
|
||||
@@ -50,16 +62,76 @@ public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAwar
|
||||
|
||||
private volatile boolean autoStartup = true;
|
||||
|
||||
private int phase = Integer.MIN_VALUE;
|
||||
private final int phase = Integer.MIN_VALUE;
|
||||
|
||||
private final Object lifecycleMonitor = new Object();
|
||||
|
||||
|
||||
/**
|
||||
* The patterns for which components will be tracked; default '*' (all trackable
|
||||
* components). Cannot be changed if {@link #isRunning()}; invoke {@link #stop()} first.
|
||||
* @param componentNamePatterns The patterns.
|
||||
*/
|
||||
public void setComponentNamePatterns(String[] componentNamePatterns) {
|
||||
Assert.notEmpty(componentNamePatterns, "componentNamePatterns must not be empty");
|
||||
this.componentNamePatterns = componentNamePatterns;
|
||||
Assert.state(!this.running, "'componentNamePatterns' cannot be changed without invoking stop() first");
|
||||
String[] trimmedAndSortedComponentNamePatterns = componentNamePatterns.clone();
|
||||
for (int i = 0; i < componentNamePatterns.length; i++) {
|
||||
trimmedAndSortedComponentNamePatterns[i] = trimmedAndSortedComponentNamePatterns[i].trim();
|
||||
}
|
||||
Arrays.sort(trimmedAndSortedComponentNamePatterns);
|
||||
Assert.isTrue(!this.componentNamePatternsExplicitlySet
|
||||
|| Arrays.equals(this.componentNamePatterns, trimmedAndSortedComponentNamePatterns),
|
||||
"When more than one message history definition " +
|
||||
"(@EnableMessageHistory or <message-history>)" +
|
||||
" is found in the context, they all must have the same 'componentNamePatterns'");
|
||||
this.componentNamePatterns = trimmedAndSortedComponentNamePatterns;
|
||||
this.componentNamePatternsExplicitlySet = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A comma-delimited list of patterns for which components will be tracked; default '*' (all trackable
|
||||
* components). Cannot be changed if {@link #isRunning()}; invoke {@link #stop()} first.
|
||||
* @param componentNamePatterns The patterns.
|
||||
*/
|
||||
@ManagedAttribute(description="comma-delimited list of patterns; must invoke stop() before changing.")
|
||||
public void setComponentNamePatternsString(String componentNamePatterns) {
|
||||
this.setComponentNamePatterns(StringUtils.delimitedListToStringArray(componentNamePatterns, ",", " "));
|
||||
}
|
||||
|
||||
@ManagedAttribute
|
||||
public String getComponentNamePatternsString() {
|
||||
return StringUtils.arrayToCommaDelimitedString(this.componentNamePatterns);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The patterns for which components will be tracked; default '*' (all trackable
|
||||
* components). Cannot be changed if {@link #isRunning()}; invoke {@link #stop()} first.
|
||||
* All members of the set must canonically represent the same patterns - allows multiple
|
||||
* EnableMessageHistory annotations as long they all have the same patterns.
|
||||
* @param componentNamePatternsSet A set of lists of comma-delimited patterns.
|
||||
*/
|
||||
public void setComponentNamePatternsSet(Set<String> componentNamePatternsSet) {
|
||||
Assert.notNull(componentNamePatternsSet, "'componentNamePatternsSet' must not be null");
|
||||
Assert.state(!this.running, "'componentNamePatternsSet' cannot be changed without invoking stop() first");
|
||||
for (String s : componentNamePatternsSet) {
|
||||
String[] componentNamePatterns = StringUtils.delimitedListToStringArray(s, "," , " ");
|
||||
Arrays.sort(componentNamePatterns);
|
||||
if (this.componentNamePatternsExplicitlySet
|
||||
&& !Arrays.equals(this.componentNamePatterns, componentNamePatterns)) {
|
||||
throw new BeanDefinitionValidationException("When more than one message history definition " +
|
||||
"(@EnableMessageHistory or <message-history>)" +
|
||||
" is found in the context, they all must have the same 'componentNamePatterns'");
|
||||
}
|
||||
else {
|
||||
this.componentNamePatterns = componentNamePatterns;
|
||||
this.componentNamePatternsExplicitlySet = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
@@ -73,18 +145,23 @@ public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAwar
|
||||
* SmartLifecycle implementation
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return this.running;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAutoStartup() {
|
||||
return this.autoStartup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPhase() {
|
||||
return this.phase;
|
||||
}
|
||||
|
||||
@ManagedOperation
|
||||
@Override
|
||||
public void start() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
if (!this.running && this.beanFactory instanceof ListableBeanFactory) {
|
||||
@@ -104,6 +181,8 @@ public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAwar
|
||||
}
|
||||
}
|
||||
|
||||
@ManagedOperation
|
||||
@Override
|
||||
public void stop() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
if (this.running && this.beanFactory instanceof ListableBeanFactory) {
|
||||
@@ -118,10 +197,12 @@ public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAwar
|
||||
}
|
||||
this.currentlyTrackedComponentNames.clear();
|
||||
this.running = false;
|
||||
this.componentNamePatternsExplicitlySet = false; // allow pattern changes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(Runnable callback) {
|
||||
this.stop();
|
||||
callback.run();
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans xmlns="http://www.springframework.org/schema/integration"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration
|
||||
http://www.springframework.org/schema/integration/spring-integration.xsd">
|
||||
|
||||
<message-history tracked-components="publishedChannel,input"/>
|
||||
|
||||
<beans:bean id="messageHistoryConfigurer"
|
||||
class="org.springframework.integration.history.MessageHistoryConfigurer">
|
||||
<beans:property name="componentNamePatterns">
|
||||
<beans:array>
|
||||
<beans:value> input </beans:value>
|
||||
<beans:value> publishedChannel </beans:value>
|
||||
</beans:array>
|
||||
</beans:property>
|
||||
</beans:bean>
|
||||
|
||||
</beans:beans>
|
||||
@@ -16,9 +16,13 @@
|
||||
|
||||
package org.springframework.integration.configuration;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -26,6 +30,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.Payload;
|
||||
import org.springframework.integration.annotation.Publisher;
|
||||
@@ -33,10 +40,15 @@ import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.integration.config.EnableMessageHistory;
|
||||
import org.springframework.integration.history.MessageHistory;
|
||||
import org.springframework.integration.history.MessageHistoryConfigurer;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.support.AnnotationConfigContextLoader;
|
||||
@@ -45,7 +57,7 @@ import org.springframework.test.context.support.AnnotationConfigContextLoader;
|
||||
* @author Artem Bilan
|
||||
* @since 4.0
|
||||
*/
|
||||
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
|
||||
@ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = {EnableIntegrationTests.ContextConfiguration.class, EnableIntegrationTests.ContextConfiguration2.class})
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class EnableIntegrationTests {
|
||||
|
||||
@@ -58,6 +70,9 @@ public class EnableIntegrationTests {
|
||||
@Autowired
|
||||
private PollableChannel publishedChannel;
|
||||
|
||||
@Autowired
|
||||
private MessageHistoryConfigurer configurer;
|
||||
|
||||
@Test
|
||||
public void testAnnotatedServiceActivator() {
|
||||
this.input.send(MessageBuilder.withPayload("Foo").build());
|
||||
@@ -65,15 +80,43 @@ public class EnableIntegrationTests {
|
||||
assertNotNull(receive);
|
||||
assertEquals("FOO", receive.getPayload());
|
||||
|
||||
MessageHistory messageHistory = receive.getHeaders().get(MessageHistory.HEADER_NAME, MessageHistory.class);
|
||||
assertNotNull(messageHistory);
|
||||
String messageHistoryString = messageHistory.toString();
|
||||
assertThat(messageHistoryString, Matchers.containsString("input"));
|
||||
assertThat(messageHistoryString, Matchers.not(Matchers.containsString("output")));
|
||||
|
||||
receive = this.publishedChannel.receive(1000);
|
||||
assertNotNull(receive);
|
||||
assertEquals("foo", receive.getPayload());
|
||||
|
||||
messageHistory = receive.getHeaders().get(MessageHistory.HEADER_NAME, MessageHistory.class);
|
||||
assertNotNull(messageHistory);
|
||||
messageHistoryString = messageHistory.toString();
|
||||
assertThat(messageHistoryString, Matchers.not(Matchers.containsString("input")));
|
||||
assertThat(messageHistoryString, Matchers.not(Matchers.containsString("output")));
|
||||
assertThat(messageHistoryString, Matchers.containsString("publishedChannel"));
|
||||
}
|
||||
|
||||
@Test @DirtiesContext
|
||||
public void testChangePatterns() {
|
||||
try {
|
||||
this.configurer.setComponentNamePatterns(new String[] {"*"});
|
||||
fail("ExpectedException");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
assertThat(e.getMessage(), containsString("cannot be changed"));
|
||||
}
|
||||
this.configurer.stop();
|
||||
this.configurer.setComponentNamePatterns(new String[] {"*"});
|
||||
assertEquals("*", TestUtils.getPropertyValue(this.configurer, "componentNamePatterns", String[].class)[0]);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ComponentScan(basePackageClasses = EnableIntegrationTests.class)
|
||||
@EnableIntegration
|
||||
@PropertySource("classpath:org/springframework/integration/configuration/EnableIntegrationTests.properties")
|
||||
@EnableMessageHistory({"input", "publishedChannel"})
|
||||
public static class ContextConfiguration {
|
||||
|
||||
@Bean
|
||||
@@ -86,6 +129,19 @@ public class EnableIntegrationTests {
|
||||
return new QueueChannel();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
@ImportResource("classpath:org/springframework/integration/configuration/EnableIntegrationTests-context.xml")
|
||||
@EnableMessageHistory("${message.history.tracked.components}")
|
||||
public static class ContextConfiguration2 {
|
||||
|
||||
@Bean
|
||||
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
||||
return new PropertySourcesPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PollableChannel publishedChannel() {
|
||||
return new QueueChannel();
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
message.history.tracked.components=input, publishedChannel
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -29,11 +29,11 @@ import java.util.Properties;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.messaging.MessageHandlingException;
|
||||
import org.springframework.integration.MessageRejectedException;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.config.ConsumerEndpointFactoryBean;
|
||||
@@ -41,6 +41,7 @@ import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageDeliveryException;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessageHandlingException;
|
||||
import org.springframework.util.StopWatch;
|
||||
|
||||
/**
|
||||
@@ -200,7 +201,7 @@ public class MessageHistoryIntegrationTests {
|
||||
Mockito.verify(handler, Mockito.times(1)).handleMessage(Mockito.any(Message.class));
|
||||
}
|
||||
|
||||
@Test(expected=BeanDefinitionParsingException.class)
|
||||
@Test(expected=BeanCreationException.class)
|
||||
public void testMessageHistoryMoreThanOneNamespaceFail() {
|
||||
new ClassPathXmlApplicationContext("messageHistoryWithHistoryWriterNamespace-fail.xml", MessageHistoryIntegrationTests.class);
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
|
||||
|
||||
<bean class="org.springframework.integration.context.MessageHistoryBeanPostProcessor"/>
|
||||
<bean class="org.springframework.integration.context.MessageHistoryBeanPostProcessor"/>
|
||||
|
||||
</beans>
|
||||
@@ -6,6 +6,6 @@
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
|
||||
|
||||
<int:message-history/>
|
||||
<int:message-history/>
|
||||
<int:message-history tracked-components="foo*"/>
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -57,14 +57,12 @@ import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.context.SmartLifecycle;
|
||||
import org.springframework.core.task.support.ExecutorServiceAdapter;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.integration.context.OrderlyShutdownCapable;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.history.MessageHistoryConfigurer;
|
||||
import org.springframework.integration.support.context.NamedComponent;
|
||||
import org.springframework.jmx.export.MBeanExporter;
|
||||
import org.springframework.jmx.export.UnableToRegisterMBeanException;
|
||||
@@ -76,6 +74,10 @@ import org.springframework.jmx.export.annotation.ManagedResource;
|
||||
import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler;
|
||||
import org.springframework.jmx.export.naming.MetadataNamingStrategy;
|
||||
import org.springframework.jmx.support.MetricType;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -179,6 +181,8 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
|
||||
|
||||
private final AtomicBoolean shuttingDown = new AtomicBoolean();
|
||||
|
||||
private MessageHistoryConfigurer messageHistoryConfigurer;
|
||||
|
||||
public IntegrationMBeanExporter() {
|
||||
super();
|
||||
// Shouldn't be necessary, but to be on the safe side...
|
||||
@@ -225,6 +229,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
|
||||
this.beanFactory = (ListableBeanFactory) beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
Assert.notNull(applicationContext, "ApplicationContext may not be null");
|
||||
@@ -236,6 +241,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
|
||||
this.shutdownExecutor = shutdownExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
|
||||
if (bean instanceof Advised) {
|
||||
@@ -249,6 +255,12 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
|
||||
}
|
||||
}
|
||||
|
||||
if (IntegrationContextUtils.INTEGRATION_MESSAGE_HISTORY_CONFIGURER.equals(beanName)
|
||||
&& bean instanceof MessageHistoryConfigurer) {
|
||||
this.messageHistoryConfigurer = (MessageHistoryConfigurer) bean;
|
||||
return bean;
|
||||
}
|
||||
|
||||
if (bean instanceof MessageHandler) {
|
||||
if (this.handlerInAnonymousWrapper(bean) != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
@@ -364,18 +376,22 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isAutoStartup() {
|
||||
return this.autoStartup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getPhase() {
|
||||
return this.phase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isRunning() {
|
||||
this.lifecycleLock.lock();
|
||||
try {
|
||||
@@ -386,6 +402,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void start() {
|
||||
this.lifecycleLock.lock();
|
||||
try {
|
||||
@@ -402,6 +419,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void stop() {
|
||||
this.lifecycleLock.lock();
|
||||
try {
|
||||
@@ -418,6 +436,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void stop(Runnable callback) {
|
||||
this.lifecycleLock.lock();
|
||||
try {
|
||||
@@ -441,6 +460,10 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
|
||||
registerHandlers();
|
||||
registerSources();
|
||||
registerEndpoints();
|
||||
if (this.messageHistoryConfigurer != null) {
|
||||
this.registerBeanInstance(this.messageHistoryConfigurer,
|
||||
IntegrationContextUtils.INTEGRATION_MESSAGE_HISTORY_CONFIGURER);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -499,6 +522,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
|
||||
* Perform orderly shutdown - called or executed from
|
||||
* {@link #stopActiveComponents(boolean, long)}.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
this.orderlyShutdownCapableComponentsBefore();
|
||||
|
||||
@@ -13,10 +13,12 @@
|
||||
http://www.springframework.org/schema/integration/jmx
|
||||
http://www.springframework.org/schema/integration/jmx/spring-integration-jmx.xsd">
|
||||
|
||||
<context:mbean-export/>
|
||||
<context:mbean-export default-domain="#{T(java.util.UUID).randomUUID().toString()}"/>
|
||||
<context:mbean-server/>
|
||||
<jmx:mbean-export default-domain="#{T(java.util.UUID).randomUUID().toString()}"/>
|
||||
|
||||
<si:message-history/>
|
||||
|
||||
<si:channel id="channel"/>
|
||||
|
||||
<jmx:notification-publishing-channel-adapter
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -16,14 +16,22 @@
|
||||
|
||||
package org.springframework.integration.jmx.config;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.management.Attribute;
|
||||
import javax.management.MBeanException;
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.Notification;
|
||||
import javax.management.ObjectInstance;
|
||||
import javax.management.ObjectName;
|
||||
|
||||
import org.junit.After;
|
||||
@@ -32,12 +40,13 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
|
||||
import org.springframework.integration.jmx.JmxHeaders;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@@ -132,6 +141,34 @@ public class NotificationPublishingChannelAdapterParserTests {
|
||||
assertEquals(1, names.size());
|
||||
}
|
||||
|
||||
@Test @DirtiesContext
|
||||
public void changeMessageHistoryPatterns() throws Exception {
|
||||
Set<ObjectInstance> mbeans = server.queryMBeans(null, null);
|
||||
boolean tested = false;
|
||||
for (ObjectInstance mbean : mbeans) {
|
||||
if (mbean.toString().contains("MessageHistoryConfigurer")) {
|
||||
ObjectName objectName = mbean.getObjectName();
|
||||
try{
|
||||
server.setAttribute(objectName, new Attribute("ComponentNamePatternsString", "foo, bar"));
|
||||
fail("Exception expected");
|
||||
}
|
||||
catch (MBeanException e) {
|
||||
assertThat(e.getTargetException(), instanceOf(IllegalStateException.class));
|
||||
assertThat(e.getTargetException().getMessage(), containsString("cannot be changed"));
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
server.invoke(objectName, "stop", new Object[]{}, new String[]{});
|
||||
server.setAttribute(objectName, new Attribute("ComponentNamePatternsString", "foo, bar"));
|
||||
assertEquals("bar,foo", server.getAttribute(objectName, "ComponentNamePatternsString"));
|
||||
tested = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertTrue(tested);
|
||||
}
|
||||
|
||||
private static class TestData {
|
||||
|
||||
}
|
||||
|
||||
@@ -59,6 +59,19 @@ assertEquals("sampleChain", chainHistory.get("name"));]]></programlisting>
|
||||
In the above example, Message History will only be maintained for all of the components that end with 'Gateway', start with 'sample',
|
||||
or match the name 'foo' exactly.
|
||||
</para>
|
||||
<para>
|
||||
Starting with <emphasis>version 4.0</emphasis>, you can also use the <code>@EnableMessageHistory</code> annotation
|
||||
in a <code>@Configuration</code> class. In addition, the <classname>MessageHistoryConfigurer</classname> bean
|
||||
is now exposed as a JMX MBean by the <classname>IntegrationMBeanExporter</classname>
|
||||
(see <xref linkend="jmx-mbean-exporter"/>), allowing the patterns to be changed at runtime.
|
||||
Note, however, that the bean must be stopped (turning off message history) in order to change the patterns.
|
||||
This feature might be useful to temporarily turn on history to analyze a system.
|
||||
The MBean's object name is <code>"<domain>:name=messageHistoryConfigurer,type=MessageHistoryConfigurer"</code>.
|
||||
</para>
|
||||
<important>
|
||||
If multiple beans (declared by <code>@EnableMessageHistory</code> and/or <code><message-history/></code>) they
|
||||
all must have identical component name patterns (when trimmed and sorted).
|
||||
</important>
|
||||
<note>
|
||||
Remember that by definition the Message History header is immutable (you can't re-write history, although some try). Therefore, when writing
|
||||
Message History values, the components are either creating brand new Messages (when the component is an origin), or they are copying the
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
>Migration Guide</ulink>.
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<section id="4.0-enable-configuration">
|
||||
<title>@EnableConfiguration</title>
|
||||
<para>
|
||||
The <code>@EnableIntegration</code> annotation has been added, to permit declaration of
|
||||
@@ -43,6 +43,14 @@
|
||||
<xref linkend="enable-integration"/> for more information.
|
||||
</para>
|
||||
</section>
|
||||
<section id="4.0-message-history">
|
||||
<title>@EnableMessageHistory</title>
|
||||
<para>
|
||||
Message history can now be enabled with the <code>@EnableMessageHistory</code> annotation in a
|
||||
<code>@Configuration</code> class; in addition the message history settings can be modified
|
||||
by a JMX MBean. For more information, see <xref linkend="message-history"/>.
|
||||
</para>
|
||||
</section>
|
||||
<section id="4.0-xpath-header-enricher-header-type">
|
||||
<title>Header Type for XPath Header Enricher</title>
|
||||
<para>
|
||||
|
||||
Reference in New Issue
Block a user