Skip to content

Commit b35dc9d

Browse files
artembilangaryrussell
authored andcommitted
Revise MessageHistory configuration
Currently the `MessageHistoryRegistrar` can parse several sources for message history - `@EnableMessageHistory` and/or `<message-history>`. Since its logic relies on the reflection it is not compatible with Spring Native. Plus it causes confusion when several sources are declared so when time comes to change something in that configuration, we may miss some place to re-align with our new requirements. Better to reject extra configurations and enforce end-users to use only one `@EnableMessageHistory` or `<message-history>`. This is actually a preferences for many other `@Enable...` in Spring portfolio. Plus we got a benefit with a Spring Native compatibility * Fix `MessageHistoryRegistrar` to parse only one `@EnableMessageHistory`. Register `MessageHistoryConfigurer` function way for Spring Native compatibility * Clean up `PublisherRegistrar` for better readability * Fix message history tests which exposed several configurations * Add JavaDoc into `EnableMessageHistory` * Refactor `MessageHistoryConfigurer` to let to override patterns configuration at runtime * Clean up `message-history.adoc`
1 parent 0fba06b commit b35dc9d

File tree

8 files changed

+123
-105
lines changed

8 files changed

+123
-105
lines changed

spring-integration-core/src/main/java/org/springframework/integration/config/EnableMessageHistory.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2019 the original author or authors.
2+
* Copyright 2014-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,9 +25,11 @@
2525
import org.springframework.context.annotation.Import;
2626

2727
/**
28-
* Enables {@link org.springframework.integration.history.MessageHistory} for Integration components.
28+
* Enables {@link org.springframework.integration.history.MessageHistory}
29+
* for Spring Integration components.
2930
*
3031
* @author Artem Bilan
32+
*
3133
* @since 4.0
3234
*/
3335
@Target(ElementType.TYPE)
@@ -36,6 +38,11 @@
3638
@Import(MessageHistoryRegistrar.class)
3739
public @interface EnableMessageHistory {
3840

41+
/**
42+
* The list of component name patterns to track (e.g. {@code "inputChannel", "out*", "*Channel", "*Service"}).
43+
* By default all Spring Integration components are tracked.
44+
* @return the list of component name patterns to track
45+
*/
3946
String[] value() default "*";
4047

4148
}
Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2019 the original author or authors.
2+
* Copyright 2014-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,19 +16,19 @@
1616

1717
package org.springframework.integration.config;
1818

19+
import java.util.Arrays;
1920
import java.util.Map;
20-
import java.util.Set;
2121

22-
import org.springframework.beans.PropertyValue;
23-
import org.springframework.beans.factory.config.BeanDefinition;
24-
import org.springframework.beans.factory.support.AbstractBeanDefinition;
25-
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
22+
import org.springframework.beans.factory.BeanDefinitionStoreException;
23+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
2624
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
27-
import org.springframework.beans.factory.support.ManagedSet;
25+
import org.springframework.beans.factory.support.RootBeanDefinition;
26+
import org.springframework.context.ConfigurableApplicationContext;
2827
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
2928
import org.springframework.core.type.AnnotationMetadata;
3029
import org.springframework.integration.context.IntegrationContextUtils;
3130
import org.springframework.integration.history.MessageHistoryConfigurer;
31+
import org.springframework.util.StringUtils;
3232

3333
/**
3434
* Registers the {@link MessageHistoryConfigurer} {@link org.springframework.beans.factory.config.BeanDefinition}
@@ -38,49 +38,60 @@
3838
*
3939
* @author Artem Bilan
4040
* @author Gary Russell
41+
*
4142
* @since 4.0
4243
*/
4344
public class MessageHistoryRegistrar implements ImportBeanDefinitionRegistrar {
4445

4546
@Override
4647
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
47-
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableMessageHistory.class.getName());
48-
Object componentNamePatterns = annotationAttributes.get("value"); // NOSONAR never null
49-
50-
if (componentNamePatterns instanceof String[]) {
51-
StringBuilder componentNamePatternsString = new StringBuilder();
52-
for (String s : (String[]) componentNamePatterns) {
53-
componentNamePatternsString.append(s).append(",");
54-
}
55-
componentNamePatterns = componentNamePatternsString.substring(0, componentNamePatternsString.length() - 1);
48+
if (registry.containsBeanDefinition(IntegrationContextUtils.INTEGRATION_MESSAGE_HISTORY_CONFIGURER_BEAN_NAME)) {
49+
throw new BeanDefinitionStoreException(
50+
"Only one @EnableMessageHistory or <message-history/> can be declared in the application context.");
5651
}
5752

58-
if (!registry.containsBeanDefinition(IntegrationContextUtils.INTEGRATION_MESSAGE_HISTORY_CONFIGURER_BEAN_NAME)) {
59-
Set<Object> componentNamePatternsSet = new ManagedSet<Object>();
60-
componentNamePatternsSet.add(componentNamePatterns);
61-
62-
AbstractBeanDefinition messageHistoryConfigurer = BeanDefinitionBuilder.genericBeanDefinition(MessageHistoryConfigurer.class)
63-
.addPropertyValue("componentNamePatternsSet", componentNamePatternsSet)
64-
.getBeanDefinition();
53+
Map<String, Object> annotationAttributes =
54+
importingClassMetadata.getAnnotationAttributes(EnableMessageHistory.class.getName());
55+
Object componentNamePatterns = annotationAttributes.get("value"); // NOSONAR never null
6556

66-
registry.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_MESSAGE_HISTORY_CONFIGURER_BEAN_NAME, messageHistoryConfigurer);
57+
String patterns;
6758

59+
if (componentNamePatterns instanceof String[]) {
60+
patterns = String.join(",", (String[]) componentNamePatterns);
6861
}
6962
else {
70-
BeanDefinition beanDefinition = registry.getBeanDefinition(IntegrationContextUtils.INTEGRATION_MESSAGE_HISTORY_CONFIGURER_BEAN_NAME);
71-
PropertyValue propertyValue = beanDefinition
72-
.getPropertyValues().getPropertyValue("componentNamePatternsSet");
73-
if (propertyValue != null) {
74-
@SuppressWarnings("unchecked")
75-
Set<Object> currentComponentNamePatternsSet = (Set<Object>) propertyValue.getValue();
76-
currentComponentNamePatternsSet.add(componentNamePatterns); // NOSONAR never null
63+
patterns = (String) componentNamePatterns;
64+
}
65+
66+
registry.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_MESSAGE_HISTORY_CONFIGURER_BEAN_NAME,
67+
new RootBeanDefinition(MessageHistoryConfigurer.class,
68+
() -> createMessageHistoryConfigurer(registry, patterns)));
69+
}
70+
71+
private MessageHistoryConfigurer createMessageHistoryConfigurer(BeanDefinitionRegistry registry, String patterns) {
72+
MessageHistoryConfigurer messageHistoryConfigurer = new MessageHistoryConfigurer();
73+
if (StringUtils.hasText(patterns)) {
74+
ConfigurableBeanFactory beanFactory = null;
75+
if (registry instanceof ConfigurableBeanFactory) {
76+
beanFactory = (ConfigurableBeanFactory) registry;
7777
}
78-
else {
79-
Set<Object> componentNamePatternsSet = new ManagedSet<Object>();
80-
componentNamePatternsSet.add(componentNamePatterns);
81-
beanDefinition.getPropertyValues().addPropertyValue("componentNamePatternsSet", componentNamePatternsSet);
78+
else if (registry instanceof ConfigurableApplicationContext) {
79+
beanFactory = ((ConfigurableApplicationContext) registry).getBeanFactory();
8280
}
81+
82+
String[] patternsToSet = StringUtils.delimitedListToStringArray(patterns, ",", " ");
83+
if (beanFactory != null) {
84+
patternsToSet =
85+
Arrays.stream(patternsToSet)
86+
.map(beanFactory::resolveEmbeddedValue)
87+
.flatMap((pattern) ->
88+
Arrays.stream(StringUtils.delimitedListToStringArray(pattern, ",", " ")))
89+
.toArray(String[]::new);
90+
}
91+
messageHistoryConfigurer.setComponentNamePatterns(patternsToSet);
8392
}
93+
94+
return messageHistoryConfigurer;
8495
}
8596

8697
}

spring-integration-core/src/main/java/org/springframework/integration/config/PublisherRegistrar.java

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -47,32 +47,31 @@ public class PublisherRegistrar implements ImportBeanDefinitionRegistrar {
4747

4848
@Override
4949
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
50+
if (registry.containsBeanDefinition(IntegrationContextUtils.PUBLISHER_ANNOTATION_POSTPROCESSOR_NAME)) {
51+
throw new BeanDefinitionStoreException("Only one enable publisher definition " +
52+
"(@EnablePublisher or <annotation-config>) can be declared in the application context.");
53+
}
5054
Map<String, Object> annotationAttributes =
5155
importingClassMetadata.getAnnotationAttributes(EnablePublisher.class.getName());
5256

53-
if (!registry.containsBeanDefinition(IntegrationContextUtils.PUBLISHER_ANNOTATION_POSTPROCESSOR_NAME)) {
54-
ConfigurableBeanFactory beanFactory;
55-
if (registry instanceof ConfigurableBeanFactory) {
56-
beanFactory = (ConfigurableBeanFactory) registry;
57-
}
58-
else if (registry instanceof ConfigurableApplicationContext) {
59-
beanFactory = ((ConfigurableApplicationContext) registry).getBeanFactory();
60-
}
61-
else {
62-
beanFactory = null;
63-
}
64-
BeanDefinitionBuilder builder =
65-
BeanDefinitionBuilder.genericBeanDefinition(PublisherAnnotationBeanPostProcessor.class,
66-
() -> createPublisherAnnotationBeanPostProcessor(annotationAttributes, beanFactory))
67-
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
68-
69-
registry.registerBeanDefinition(IntegrationContextUtils.PUBLISHER_ANNOTATION_POSTPROCESSOR_NAME,
70-
builder.getBeanDefinition());
57+
ConfigurableBeanFactory beanFactory;
58+
if (registry instanceof ConfigurableBeanFactory) {
59+
beanFactory = (ConfigurableBeanFactory) registry;
60+
}
61+
else if (registry instanceof ConfigurableApplicationContext) {
62+
beanFactory = ((ConfigurableApplicationContext) registry).getBeanFactory();
7163
}
7264
else {
73-
throw new BeanDefinitionStoreException("Only one enable publisher definition " +
74-
"(@EnablePublisher or <annotation-config>) can be declared in the application context.");
65+
beanFactory = null;
7566
}
67+
68+
BeanDefinitionBuilder builder =
69+
BeanDefinitionBuilder.genericBeanDefinition(PublisherAnnotationBeanPostProcessor.class,
70+
() -> createPublisherAnnotationBeanPostProcessor(annotationAttributes, beanFactory))
71+
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
72+
73+
registry.registerBeanDefinition(IntegrationContextUtils.PUBLISHER_ANNOTATION_POSTPROCESSOR_NAME,
74+
builder.getBeanDefinition());
7675
}
7776

7877
private PublisherAnnotationBeanPostProcessor createPublisherAnnotationBeanPostProcessor(

spring-integration-core/src/main/java/org/springframework/integration/history/MessageHistoryConfigurer.java

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,7 +30,6 @@
3030
import org.springframework.beans.factory.BeanFactoryUtils;
3131
import org.springframework.beans.factory.ListableBeanFactory;
3232
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
33-
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
3433
import org.springframework.integration.support.management.IntegrationManagedResource;
3534
import org.springframework.integration.support.management.ManageableSmartLifecycle;
3635
import org.springframework.integration.support.management.TrackableComponent;
@@ -57,9 +56,7 @@ public class MessageHistoryConfigurer implements ManageableSmartLifecycle, BeanF
5756

5857
private final Set<TrackableComponent> currentlyTrackedComponents = ConcurrentHashMap.newKeySet();
5958

60-
private String[] componentNamePatterns = new String[]{ "*" };
61-
62-
private boolean componentNamePatternsExplicitlySet;
59+
private String[] componentNamePatterns = { "*" };
6360

6461
private ListableBeanFactory beanFactory;
6562

@@ -83,13 +80,7 @@ public void setComponentNamePatterns(String[] componentNamePatterns) {
8380
trimmedAndSortedComponentNamePatterns[i] = trimmedAndSortedComponentNamePatterns[i].trim();
8481
}
8582
Arrays.sort(trimmedAndSortedComponentNamePatterns);
86-
Assert.isTrue(!this.componentNamePatternsExplicitlySet
87-
|| Arrays.equals(this.componentNamePatterns, trimmedAndSortedComponentNamePatterns),
88-
"When more than one message history definition " +
89-
"(@EnableMessageHistory or <message-history>)" +
90-
" is found in the context, they all must have the same 'componentNamePatterns'");
9183
this.componentNamePatterns = trimmedAndSortedComponentNamePatterns;
92-
this.componentNamePatternsExplicitlySet = true;
9384
}
9485

9586
/**
@@ -118,20 +109,8 @@ public String getComponentNamePatternsString() {
118109
public void setComponentNamePatternsSet(Set<String> componentNamePatternsSet) {
119110
Assert.notNull(componentNamePatternsSet, "'componentNamePatternsSet' must not be null");
120111
Assert.state(!this.running, "'componentNamePatternsSet' cannot be changed without invoking stop() first");
121-
for (String s : componentNamePatternsSet) {
122-
String[] patterns = StringUtils.delimitedListToStringArray(s, ",", " ");
123-
Arrays.sort(patterns);
124-
if (this.componentNamePatternsExplicitlySet
125-
&& !Arrays.equals(this.componentNamePatterns, patterns)) {
126-
throw new BeanDefinitionValidationException("When more than one message history definition " +
127-
"(@EnableMessageHistory or <message-history>)" +
128-
" is found in the context, they all must have the same 'componentNamePatterns'");
129-
}
130-
else {
131-
this.componentNamePatterns = patterns;
132-
this.componentNamePatternsExplicitlySet = true;
133-
}
134-
}
112+
String patterns = String.join(",", componentNamePatternsSet);
113+
this.componentNamePatterns = StringUtils.delimitedListToStringArray(patterns, ",", " ");
135114
}
136115

137116
@Override
@@ -225,7 +204,6 @@ public void stop() {
225204
});
226205

227206
this.currentlyTrackedComponents.clear();
228-
this.componentNamePatternsExplicitlySet = false; // allow pattern changes
229207
this.running = false;
230208
}
231209
}

spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests-context.xml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
<!--INT-3853 -->
1313
<context:property-placeholder location="classpath:org/springframework/integration/configuration/EnableIntegrationTests.properties"/>
1414

15-
<message-history tracked-components="publishedChannel,input,annotationTestService*"/>
16-
1715
<channel-interceptor pattern="none">
1816
<wire-tap channel="bar" />
1917
</channel-interceptor>

spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,6 @@ public void testAdvisedServiceActivator() {
800800
@EnableIntegration
801801
// INT-3853
802802
// @PropertySource("classpath:org/springframework/integration/configuration/EnableIntegrationTests.properties")
803-
@EnableMessageHistory({ "input", "publishedChannel", "annotationTestService*" })
804803
public static class ContextConfiguration {
805804

806805
@Bean

spring-integration-core/src/test/java/org/springframework/integration/history/MessageHistoryIntegrationTests.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@
3030
import org.mockito.Mockito;
3131

3232
import org.springframework.beans.DirectFieldAccessor;
33-
import org.springframework.beans.factory.BeanCreationException;
33+
import org.springframework.beans.factory.BeanDefinitionStoreException;
3434
import org.springframework.context.ConfigurableApplicationContext;
3535
import org.springframework.context.support.ClassPathXmlApplicationContext;
3636
import org.springframework.integration.channel.DirectChannel;
@@ -57,9 +57,8 @@ public void testNoHistoryAwareMessageHandler() {
5757
Map<String, ConsumerEndpointFactoryBean> cefBeans = ac.getBeansOfType(ConsumerEndpointFactoryBean.class);
5858
for (ConsumerEndpointFactoryBean cefBean : cefBeans.values()) {
5959
DirectFieldAccessor bridgeAccessor = new DirectFieldAccessor(cefBean);
60-
String handlerClassName = bridgeAccessor.getPropertyValue("handler").getClass().getName();
61-
assertThat("org.springframework.integration.config.MessageHistoryWritingMessageHandler"
62-
.equals(handlerClassName)).isFalse();
60+
Boolean shouldTrack = (Boolean) bridgeAccessor.getPropertyValue("handler.shouldTrack");
61+
assertThat(shouldTrack).isFalse();
6362
}
6463
ac.close();
6564
}
@@ -226,7 +225,7 @@ public void handleMessage(Message<?> message) {
226225

227226
@Test
228227
public void testMessageHistoryMoreThanOneNamespaceFail() {
229-
assertThatExceptionOfType(BeanCreationException.class)
228+
assertThatExceptionOfType(BeanDefinitionStoreException.class)
230229
.isThrownBy(() ->
231230
new ClassPathXmlApplicationContext("messageHistoryWithHistoryWriterNamespace-fail.xml",
232231
MessageHistoryIntegrationTests.class));

0 commit comments

Comments
 (0)