Skip to content

Commit 07c32ae

Browse files
artembilangaryrussell
authored andcommitted
INT-4489: MessageHistory: Allow runtime beans
JIRA: https://jira.spring.io/browse/INT-4489 Make `MessageHistoryConfigurer` as a `BeanPostProcessor` and apply tracking logic to processed beans if it is already started. **Cherry-pick to 5.0.x**
1 parent e6a35c4 commit 07c32ae

File tree

3 files changed

+74
-46
lines changed

3 files changed

+74
-46
lines changed

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

Lines changed: 63 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -18,8 +18,8 @@
1818

1919
import java.util.Arrays;
2020
import java.util.Collection;
21-
import java.util.HashSet;
2221
import java.util.Set;
22+
import java.util.concurrent.ConcurrentHashMap;
2323

2424
import org.apache.commons.logging.Log;
2525
import org.apache.commons.logging.LogFactory;
@@ -29,6 +29,7 @@
2929
import org.springframework.beans.factory.BeanFactoryAware;
3030
import org.springframework.beans.factory.BeanFactoryUtils;
3131
import org.springframework.beans.factory.ListableBeanFactory;
32+
import org.springframework.beans.factory.config.BeanPostProcessor;
3233
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
3334
import org.springframework.context.SmartLifecycle;
3435
import org.springframework.integration.support.management.IntegrationManagedResource;
@@ -49,25 +50,23 @@
4950
*/
5051
@ManagedResource
5152
@IntegrationManagedResource
52-
public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAware {
53+
public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAware, BeanPostProcessor {
5354

5455
private final Log logger = LogFactory.getLog(this.getClass());
5556

56-
private volatile String[] componentNamePatterns = new String[] { "*" };
57+
private final Set<TrackableComponent> currentlyTrackedComponents = ConcurrentHashMap.newKeySet();
5758

58-
private volatile boolean componentNamePatternsExplicitlySet;
59+
private String[] componentNamePatterns = new String[] { "*" };
5960

60-
private final Set<String> currentlyTrackedComponentNames = new HashSet<String>();
61+
private boolean componentNamePatternsExplicitlySet;
6162

62-
private volatile BeanFactory beanFactory;
63+
private ListableBeanFactory beanFactory;
6364

64-
private volatile boolean running;
65-
66-
private volatile boolean autoStartup = true;
65+
private boolean autoStartup = true;
6766

68-
private final int phase = Integer.MIN_VALUE;
67+
private int phase = Integer.MIN_VALUE;
6968

70-
private final Object lifecycleMonitor = new Object();
69+
private volatile boolean running;
7170

7271

7372
/**
@@ -84,10 +83,10 @@ public void setComponentNamePatterns(String[] componentNamePatterns) {
8483
}
8584
Arrays.sort(trimmedAndSortedComponentNamePatterns);
8685
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'");
86+
|| Arrays.equals(this.componentNamePatterns, trimmedAndSortedComponentNamePatterns),
87+
"When more than one message history definition " +
88+
"(@EnableMessageHistory or <message-history>)" +
89+
" is found in the context, they all must have the same 'componentNamePatterns'");
9190
this.componentNamePatterns = trimmedAndSortedComponentNamePatterns;
9291
this.componentNamePatternsExplicitlySet = true;
9392
}
@@ -136,13 +135,30 @@ public void setComponentNamePatternsSet(Set<String> componentNamePatternsSet) {
136135

137136
@Override
138137
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
139-
this.beanFactory = beanFactory;
138+
Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
139+
"The provided 'beanFactory' must be of 'ListableBeanFactory' type.");
140+
this.beanFactory = (ListableBeanFactory) beanFactory;
140141
}
141142

142-
private static Collection<TrackableComponent> getTrackableComponents(ListableBeanFactory beanFactory) {
143-
return BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory, TrackableComponent.class).values();
143+
@Override
144+
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
145+
if (bean instanceof TrackableComponent && this.running) {
146+
trackComponentIfAny((TrackableComponent) bean);
147+
}
148+
return bean;
144149
}
145150

151+
private void trackComponentIfAny(TrackableComponent component) {
152+
String componentName = component.getComponentName();
153+
boolean shouldTrack = PatternMatchUtils.simpleMatch(this.componentNamePatterns, componentName);
154+
component.setShouldTrack(shouldTrack);
155+
if (shouldTrack) {
156+
this.currentlyTrackedComponents.add(component);
157+
if (this.logger.isInfoEnabled()) {
158+
this.logger.info("Enabling MessageHistory tracking for component '" + componentName + "'");
159+
}
160+
}
161+
}
146162

147163
/*
148164
* SmartLifecycle implementation
@@ -153,11 +169,19 @@ public boolean isRunning() {
153169
return this.running;
154170
}
155171

172+
public void setAutoStartup(boolean autoStartup) {
173+
this.autoStartup = autoStartup;
174+
}
175+
156176
@Override
157177
public boolean isAutoStartup() {
158178
return this.autoStartup;
159179
}
160180

181+
public void setPhase(int phase) {
182+
this.phase = phase;
183+
}
184+
161185
@Override
162186
public int getPhase() {
163187
return this.phase;
@@ -166,41 +190,32 @@ public int getPhase() {
166190
@ManagedOperation
167191
@Override
168192
public void start() {
169-
synchronized (this.lifecycleMonitor) {
170-
if (!this.running && this.beanFactory instanceof ListableBeanFactory) {
171-
for (TrackableComponent component : getTrackableComponents((ListableBeanFactory) this.beanFactory)) {
172-
String componentName = component.getComponentName();
173-
boolean shouldTrack = PatternMatchUtils.simpleMatch(this.componentNamePatterns, componentName);
174-
component.setShouldTrack(shouldTrack);
175-
if (shouldTrack) {
176-
this.currentlyTrackedComponentNames.add(componentName);
177-
if (this.logger.isInfoEnabled()) {
178-
this.logger.info("Enabling MessageHistory tracking for component '" + componentName + "'");
179-
}
180-
}
193+
synchronized (this.currentlyTrackedComponents) {
194+
if (!this.running) {
195+
for (TrackableComponent component : getTrackableComponents(this.beanFactory)) {
196+
trackComponentIfAny(component);
197+
this.running = true;
181198
}
182-
this.running = true;
183199
}
184200
}
185201
}
186202

187203
@ManagedOperation
188204
@Override
189205
public void stop() {
190-
synchronized (this.lifecycleMonitor) {
191-
if (this.running && this.beanFactory instanceof ListableBeanFactory) {
192-
for (TrackableComponent component : getTrackableComponents((ListableBeanFactory) this.beanFactory)) {
193-
String componentName = component.getComponentName();
194-
if (this.currentlyTrackedComponentNames.contains(componentName)) {
195-
component.setShouldTrack(false);
196-
if (this.logger.isInfoEnabled()) {
197-
this.logger.info("Disabling MessageHistory tracking for component '" + componentName + "'");
198-
}
206+
synchronized (this.currentlyTrackedComponents) {
207+
if (this.running) {
208+
this.currentlyTrackedComponents.forEach(component -> {
209+
component.setShouldTrack(false);
210+
if (this.logger.isInfoEnabled()) {
211+
this.logger.info("Disabling MessageHistory tracking for component '"
212+
+ component.getComponentName() + "'");
199213
}
200-
}
201-
this.currentlyTrackedComponentNames.clear();
202-
this.running = false;
214+
});
215+
216+
this.currentlyTrackedComponents.clear();
203217
this.componentNamePatternsExplicitlySet = false; // allow pattern changes
218+
this.running = false;
204219
}
205220
}
206221
}
@@ -211,4 +226,8 @@ public void stop(Runnable callback) {
211226
callback.run();
212227
}
213228

229+
private static Collection<TrackableComponent> getTrackableComponents(ListableBeanFactory beanFactory) {
230+
return BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory, TrackableComponent.class).values();
231+
}
232+
214233
}

spring-integration-core/src/test/java/org/springframework/integration/dsl/manualflow/ManualFlowTests.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import java.util.concurrent.atomic.AtomicReference;
4242
import java.util.function.Supplier;
4343

44+
import org.hamcrest.Matchers;
4445
import org.junit.Test;
4546
import org.junit.runner.RunWith;
4647

@@ -58,6 +59,7 @@
5859
import org.springframework.context.annotation.Scope;
5960
import org.springframework.integration.channel.QueueChannel;
6061
import org.springframework.integration.config.EnableIntegration;
62+
import org.springframework.integration.config.EnableMessageHistory;
6163
import org.springframework.integration.core.MessageProducer;
6264
import org.springframework.integration.core.MessagingTemplate;
6365
import org.springframework.integration.dsl.IntegrationFlow;
@@ -71,6 +73,7 @@
7173
import org.springframework.integration.dsl.context.IntegrationFlowContext.IntegrationFlowRegistration;
7274
import org.springframework.integration.endpoint.MessageProducerSupport;
7375
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
76+
import org.springframework.integration.history.MessageHistory;
7477
import org.springframework.integration.support.SmartLifecycleRoleController;
7578
import org.springframework.integration.transformer.MessageTransformingHandler;
7679
import org.springframework.messaging.Message;
@@ -254,6 +257,12 @@ public void testDynamicSubFlow() {
254257
assertNotNull(receive);
255258
assertEquals("test", receive.getPayload());
256259

260+
MessageHistory messageHistory = MessageHistory.read(receive);
261+
assertNotNull(messageHistory);
262+
String messageHistoryString = messageHistory.toString();
263+
assertThat(messageHistoryString, Matchers.containsString("dynamicFlow.input"));
264+
assertThat(messageHistoryString, Matchers.containsString("dynamicFlow.subFlow#0.channel#1"));
265+
257266
this.integrationFlowContext.remove("dynamicFlow");
258267
}
259268

@@ -475,6 +484,7 @@ public void testConcurrentRegistration() throws InterruptedException {
475484

476485
@Configuration
477486
@EnableIntegration
487+
@EnableMessageHistory
478488
public static class RootConfiguration {
479489

480490
@Bean

src/reference/asciidoc/message-history.adoc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,7 @@ This feature might be useful to temporarily turn on history to analyze a system.
7373
The MBean's object name is `"<domain>:name=messageHistoryConfigurer,type=MessageHistoryConfigurer"`.
7474

7575
IMPORTANT: If multiple beans (declared by `@EnableMessageHistory` and/or `<message-history/>`) they all must have identical component name patterns (when trimmed and sorted).
76-
*Do not use a generic
77-
`<bean/>` definition for the `MessageHistoryConfigurer`*.
76+
*Do not use a generic `<bean/>` definition for the `MessageHistoryConfigurer`*.
7877

7978
NOTE: Remember that by definition the Message History header is immutable (you can't re-write history, although some try).
8079
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 history from a request Message, modifying it and setting the new list on a reply Message.

0 commit comments

Comments
 (0)