From 07c32ae93b72f7df68e3255dcf87223a1e9e4b95 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Fri, 22 Jun 2018 13:26:08 -0400 Subject: [PATCH] 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** --- .../history/MessageHistoryConfigurer.java | 109 ++++++++++-------- .../dsl/manualflow/ManualFlowTests.java | 10 ++ src/reference/asciidoc/message-history.adoc | 3 +- 3 files changed, 75 insertions(+), 47 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/history/MessageHistoryConfigurer.java b/spring-integration-core/src/main/java/org/springframework/integration/history/MessageHistoryConfigurer.java index 77ada59ea3..3112e886b3 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/history/MessageHistoryConfigurer.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/history/MessageHistoryConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -18,8 +18,8 @@ package org.springframework.integration.history; import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -29,6 +29,7 @@ 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.config.BeanPostProcessor; import org.springframework.beans.factory.support.BeanDefinitionValidationException; import org.springframework.context.SmartLifecycle; import org.springframework.integration.support.management.IntegrationManagedResource; @@ -49,26 +50,24 @@ import org.springframework.util.StringUtils; */ @ManagedResource @IntegrationManagedResource -public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAware { +public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAware, BeanPostProcessor { private final Log logger = LogFactory.getLog(this.getClass()); - private volatile String[] componentNamePatterns = new String[] { "*" }; + private final Set currentlyTrackedComponents = ConcurrentHashMap.newKeySet(); - private volatile boolean componentNamePatternsExplicitlySet; + private String[] componentNamePatterns = new String[] { "*" }; - private final Set currentlyTrackedComponentNames = new HashSet(); + private boolean componentNamePatternsExplicitlySet; - private volatile BeanFactory beanFactory; + private ListableBeanFactory beanFactory; + + private boolean autoStartup = true; + + private int phase = Integer.MIN_VALUE; private volatile boolean running; - private volatile boolean autoStartup = true; - - private final int phase = Integer.MIN_VALUE; - - private final Object lifecycleMonitor = new Object(); - /** * The patterns for which components will be tracked; default '*' (all trackable @@ -84,10 +83,10 @@ public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAwar } Arrays.sort(trimmedAndSortedComponentNamePatterns); Assert.isTrue(!this.componentNamePatternsExplicitlySet - || Arrays.equals(this.componentNamePatterns, trimmedAndSortedComponentNamePatterns), - "When more than one message history definition " + - "(@EnableMessageHistory or )" + - " is found in the context, they all must have the same 'componentNamePatterns'"); + || Arrays.equals(this.componentNamePatterns, trimmedAndSortedComponentNamePatterns), + "When more than one message history definition " + + "(@EnableMessageHistory or )" + + " is found in the context, they all must have the same 'componentNamePatterns'"); this.componentNamePatterns = trimmedAndSortedComponentNamePatterns; this.componentNamePatternsExplicitlySet = true; } @@ -136,13 +135,30 @@ public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAwar @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; + Assert.isInstanceOf(ListableBeanFactory.class, beanFactory, + "The provided 'beanFactory' must be of 'ListableBeanFactory' type."); + this.beanFactory = (ListableBeanFactory) beanFactory; } - private static Collection getTrackableComponents(ListableBeanFactory beanFactory) { - return BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory, TrackableComponent.class).values(); + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof TrackableComponent && this.running) { + trackComponentIfAny((TrackableComponent) bean); + } + return bean; } + private void trackComponentIfAny(TrackableComponent component) { + String componentName = component.getComponentName(); + boolean shouldTrack = PatternMatchUtils.simpleMatch(this.componentNamePatterns, componentName); + component.setShouldTrack(shouldTrack); + if (shouldTrack) { + this.currentlyTrackedComponents.add(component); + if (this.logger.isInfoEnabled()) { + this.logger.info("Enabling MessageHistory tracking for component '" + componentName + "'"); + } + } + } /* * SmartLifecycle implementation @@ -153,11 +169,19 @@ public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAwar return this.running; } + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + @Override public boolean isAutoStartup() { return this.autoStartup; } + public void setPhase(int phase) { + this.phase = phase; + } + @Override public int getPhase() { return this.phase; @@ -166,20 +190,12 @@ public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAwar @ManagedOperation @Override public void start() { - synchronized (this.lifecycleMonitor) { - if (!this.running && this.beanFactory instanceof ListableBeanFactory) { - for (TrackableComponent component : getTrackableComponents((ListableBeanFactory) this.beanFactory)) { - String componentName = component.getComponentName(); - boolean shouldTrack = PatternMatchUtils.simpleMatch(this.componentNamePatterns, componentName); - component.setShouldTrack(shouldTrack); - if (shouldTrack) { - this.currentlyTrackedComponentNames.add(componentName); - if (this.logger.isInfoEnabled()) { - this.logger.info("Enabling MessageHistory tracking for component '" + componentName + "'"); - } - } + synchronized (this.currentlyTrackedComponents) { + if (!this.running) { + for (TrackableComponent component : getTrackableComponents(this.beanFactory)) { + trackComponentIfAny(component); + this.running = true; } - this.running = true; } } } @@ -187,20 +203,19 @@ public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAwar @ManagedOperation @Override public void stop() { - synchronized (this.lifecycleMonitor) { - if (this.running && this.beanFactory instanceof ListableBeanFactory) { - for (TrackableComponent component : getTrackableComponents((ListableBeanFactory) this.beanFactory)) { - String componentName = component.getComponentName(); - if (this.currentlyTrackedComponentNames.contains(componentName)) { - component.setShouldTrack(false); - if (this.logger.isInfoEnabled()) { - this.logger.info("Disabling MessageHistory tracking for component '" + componentName + "'"); - } + synchronized (this.currentlyTrackedComponents) { + if (this.running) { + this.currentlyTrackedComponents.forEach(component -> { + component.setShouldTrack(false); + if (this.logger.isInfoEnabled()) { + this.logger.info("Disabling MessageHistory tracking for component '" + + component.getComponentName() + "'"); } - } - this.currentlyTrackedComponentNames.clear(); - this.running = false; + }); + + this.currentlyTrackedComponents.clear(); this.componentNamePatternsExplicitlySet = false; // allow pattern changes + this.running = false; } } } @@ -211,4 +226,8 @@ public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAwar callback.run(); } + private static Collection getTrackableComponents(ListableBeanFactory beanFactory) { + return BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory, TrackableComponent.class).values(); + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dsl/manualflow/ManualFlowTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dsl/manualflow/ManualFlowTests.java index 7365a9f61f..2ac5a79949 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/dsl/manualflow/ManualFlowTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/dsl/manualflow/ManualFlowTests.java @@ -41,6 +41,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; +import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; @@ -58,6 +59,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.config.EnableMessageHistory; import org.springframework.integration.core.MessageProducer; import org.springframework.integration.core.MessagingTemplate; import org.springframework.integration.dsl.IntegrationFlow; @@ -71,6 +73,7 @@ import org.springframework.integration.dsl.context.IntegrationFlowContext; import org.springframework.integration.dsl.context.IntegrationFlowContext.IntegrationFlowRegistration; import org.springframework.integration.endpoint.MessageProducerSupport; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.integration.history.MessageHistory; import org.springframework.integration.support.SmartLifecycleRoleController; import org.springframework.integration.transformer.MessageTransformingHandler; import org.springframework.messaging.Message; @@ -254,6 +257,12 @@ public class ManualFlowTests { assertNotNull(receive); assertEquals("test", receive.getPayload()); + MessageHistory messageHistory = MessageHistory.read(receive); + assertNotNull(messageHistory); + String messageHistoryString = messageHistory.toString(); + assertThat(messageHistoryString, Matchers.containsString("dynamicFlow.input")); + assertThat(messageHistoryString, Matchers.containsString("dynamicFlow.subFlow#0.channel#1")); + this.integrationFlowContext.remove("dynamicFlow"); } @@ -475,6 +484,7 @@ public class ManualFlowTests { @Configuration @EnableIntegration + @EnableMessageHistory public static class RootConfiguration { @Bean diff --git a/src/reference/asciidoc/message-history.adoc b/src/reference/asciidoc/message-history.adoc index d335883234..7bf4effb71 100644 --- a/src/reference/asciidoc/message-history.adoc +++ b/src/reference/asciidoc/message-history.adoc @@ -73,8 +73,7 @@ This feature might be useful to temporarily turn on history to analyze a system. The MBean's object name is `":name=messageHistoryConfigurer,type=MessageHistoryConfigurer"`. IMPORTANT: If multiple beans (declared by `@EnableMessageHistory` and/or ``) they all must have identical component name patterns (when trimmed and sorted). -*Do not use a generic - `` definition for the `MessageHistoryConfigurer`*. +*Do not use a generic `` definition for the `MessageHistoryConfigurer`*. 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 history from a request Message, modifying it and setting the new list on a reply Message.