GH-9381: Introduce Control Bus commands management
Fixes: #9381 Currently, there is no way to know in one place what Control Bus commands are available and with what arguments * Add `ControlBusCommandRegistry` infrastructure bean to gather control bus commands from beans and expose them for invocation * Add `ControlBusController` to expose a `/control-bus` REST service against the mentioned `ControlBusCommandRegistry` * Add `@EnableIntegrationManagement(loadControlBusCommands)` to be able to load all the Control Bus commands from the application context instead of on demand by default * Deprecated existing SpEL(and Groovy)-based Control Bus functionality in favor of new, more manageable, logic
This commit is contained in:
@@ -80,10 +80,15 @@ public class IntegrationMessageHeaderAccessor extends MessageHeaderAccessor {
|
||||
public static final String SOURCE_DATA = "sourceData";
|
||||
|
||||
/**
|
||||
* Raw source message.
|
||||
* The header for {@link reactor.util.context.ContextView}.
|
||||
*/
|
||||
public static final String REACTOR_CONTEXT = "reactorContext";
|
||||
|
||||
/**
|
||||
* The header for Control Bus command arguments. Must be a list of values.
|
||||
*/
|
||||
public static final String CONTROL_BUS_ARGUMENTS = "controlBusArguments";
|
||||
|
||||
private static final BiFunction<String, String, String> TYPE_VERIFY_MESSAGE_FUNCTION =
|
||||
(name, trailer) -> "The '" + name + trailer;
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2024 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
|
||||
*
|
||||
* https://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 org.springframework.integration.handler.ControlBusMessageProcessor;
|
||||
import org.springframework.integration.handler.ServiceActivatingHandler;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
|
||||
/**
|
||||
* FactoryBean for creating {@link MessageHandler} instances to handle a message with a Control Bus command.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 6.4
|
||||
*/
|
||||
public class ControlBusFactoryBean extends AbstractSimpleMessageHandlerFactoryBean<MessageHandler> {
|
||||
|
||||
private Long sendTimeout;
|
||||
|
||||
public void setSendTimeout(Long sendTimeout) {
|
||||
this.sendTimeout = sendTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MessageHandler createHandler() {
|
||||
ServiceActivatingHandler handler = new ServiceActivatingHandler(new ControlBusMessageProcessor());
|
||||
if (this.sendTimeout != null) {
|
||||
handler.setSendTimeout(this.sendTimeout);
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -53,6 +53,7 @@ import org.springframework.integration.support.channel.BeanFactoryChannelResolve
|
||||
import org.springframework.integration.support.channel.ChannelResolverUtils;
|
||||
import org.springframework.integration.support.converter.ConfigurableCompositeMessageConverter;
|
||||
import org.springframework.integration.support.converter.DefaultDatatypeChannelMessageConverter;
|
||||
import org.springframework.integration.support.management.ControlBusCommandRegistry;
|
||||
import org.springframework.integration.support.utils.IntegrationUtils;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -127,6 +128,7 @@ public class DefaultConfiguringBeanFactoryPostProcessor implements BeanDefinitio
|
||||
registerMessageHandlerMethodFactory();
|
||||
registerListMessageHandlerMethodFactory();
|
||||
registerIntegrationConfigurationReport();
|
||||
registerControlBusCommandRegistry();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -440,6 +442,17 @@ public class DefaultConfiguringBeanFactoryPostProcessor implements BeanDefinitio
|
||||
.getBeanDefinition());
|
||||
}
|
||||
|
||||
private void registerControlBusCommandRegistry() {
|
||||
if (!this.beanFactory.containsBean(IntegrationContextUtils.CONTROL_BUS_COMMAND_REGISTRY_BEAN_NAME)) {
|
||||
BeanDefinitionBuilder builder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(ControlBusCommandRegistry.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
|
||||
this.registry.registerBeanDefinition(IntegrationContextUtils.CONTROL_BUS_COMMAND_REGISTRY_BEAN_NAME,
|
||||
builder.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
private static BeanDefinitionBuilder createMessageHandlerMethodFactoryBeanDefinition(boolean listCapable) {
|
||||
return BeanDefinitionBuilder.genericBeanDefinition(IntegrationMessageHandlerMethodFactory.class)
|
||||
.addConstructorArgValue(listCapable)
|
||||
|
||||
@@ -73,4 +73,12 @@ public @interface EnableIntegrationManagement {
|
||||
*/
|
||||
String[] observationPatterns() default {};
|
||||
|
||||
/**
|
||||
* Set to {@code true} to turn on Control Bus commands loading after application context initialization.
|
||||
* @return the flag to initialize the control bus registry eagerly or not.
|
||||
* @since 6.4
|
||||
* @see org.springframework.integration.support.management.ControlBusCommandRegistry#setEagerInitialization(boolean)
|
||||
*/
|
||||
String loadControlBusCommands() default "false";
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -17,8 +17,6 @@
|
||||
package org.springframework.integration.config;
|
||||
|
||||
import org.springframework.expression.MethodFilter;
|
||||
import org.springframework.integration.expression.ControlBusMethodFilter;
|
||||
import org.springframework.integration.handler.ExpressionCommandMessageProcessor;
|
||||
import org.springframework.integration.handler.ServiceActivatingHandler;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
|
||||
@@ -31,10 +29,14 @@ import org.springframework.messaging.MessageHandler;
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*
|
||||
* @deprecated in favor of {@link ControlBusFactoryBean}
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
public class ExpressionControlBusFactoryBean extends AbstractSimpleMessageHandlerFactoryBean<MessageHandler> {
|
||||
|
||||
private static final MethodFilter METHOD_FILTER = new ControlBusMethodFilter();
|
||||
@SuppressWarnings("removal")
|
||||
private static final MethodFilter METHOD_FILTER = new org.springframework.integration.expression.ControlBusMethodFilter();
|
||||
|
||||
private Long sendTimeout;
|
||||
|
||||
@@ -42,10 +44,11 @@ public class ExpressionControlBusFactoryBean extends AbstractSimpleMessageHandle
|
||||
this.sendTimeout = sendTimeout;
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
@Override
|
||||
protected MessageHandler createHandler() {
|
||||
ExpressionCommandMessageProcessor processor =
|
||||
new ExpressionCommandMessageProcessor(METHOD_FILTER, getBeanFactory());
|
||||
org.springframework.integration.handler.ExpressionCommandMessageProcessor processor =
|
||||
new org.springframework.integration.handler.ExpressionCommandMessageProcessor(METHOD_FILTER, getBeanFactory());
|
||||
ServiceActivatingHandler handler = new ServiceActivatingHandler(processor);
|
||||
if (this.sendTimeout != null) {
|
||||
handler.setSendTimeout(this.sendTimeout);
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.springframework.context.annotation.Role;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.integration.support.management.ControlBusCommandRegistry;
|
||||
import org.springframework.integration.support.management.metrics.MetricsCaptor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -52,10 +53,16 @@ import org.springframework.util.StringUtils;
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public class IntegrationManagementConfiguration implements ImportAware, EnvironmentAware {
|
||||
|
||||
private final ControlBusCommandRegistry controlBusCommandRegistry;
|
||||
|
||||
private AnnotationAttributes attributes;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
public IntegrationManagementConfiguration(ControlBusCommandRegistry controlBusCommandRegistry) {
|
||||
this.controlBusCommandRegistry = controlBusCommandRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
@@ -67,6 +74,9 @@ public class IntegrationManagementConfiguration implements ImportAware, Environm
|
||||
this.attributes = AnnotationAttributes.fromMap(map);
|
||||
Assert.notNull(this.attributes, () ->
|
||||
"@EnableIntegrationManagement is not present on importing class " + importMetadata.getClassName());
|
||||
this.controlBusCommandRegistry.setEagerInitialization(
|
||||
Boolean.parseBoolean(
|
||||
this.environment.resolvePlaceholders(this.attributes.getString("loadControlBusCommands"))));
|
||||
}
|
||||
|
||||
@Bean(name = IntegrationManagementConfigurer.MANAGEMENT_CONFIGURER_NAME)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -20,19 +20,30 @@ import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.integration.config.ControlBusFactoryBean;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class ControlBusParser extends AbstractConsumerEndpointParser {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("removal")
|
||||
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
"org.springframework.integration.config.ExpressionControlBusFactoryBean");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "bean-resolver");
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ControlBusFactoryBean.class);
|
||||
if (Boolean.FALSE.equals(Boolean.parseBoolean(element.getAttribute("use-registry")))) {
|
||||
builder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
org.springframework.integration.config.ExpressionControlBusFactoryBean.class);
|
||||
parserContext.getReaderContext()
|
||||
.warning("The 'ExpressionControlBusFactoryBean' for '<control-bus>' is deprecated (for removal) " +
|
||||
"in favor of 'ControlBusFactoryBean'. " +
|
||||
"Set 'use-registry' attribute to 'true' to switch to a new functionality.",
|
||||
element);
|
||||
}
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "order");
|
||||
return builder;
|
||||
|
||||
@@ -100,6 +100,8 @@ public abstract class IntegrationContextUtils {
|
||||
|
||||
public static final String LIST_MESSAGE_HANDLER_FACTORY_BEAN_NAME = "integrationListMessageHandlerMethodFactory";
|
||||
|
||||
public static final String CONTROL_BUS_COMMAND_REGISTRY_BEAN_NAME = "controlBusCommandRegistry";
|
||||
|
||||
/**
|
||||
* The default timeout for blocking operations like send and receive messages.
|
||||
* @since 6.1
|
||||
|
||||
@@ -52,7 +52,6 @@ import org.springframework.integration.core.MessageProducer;
|
||||
import org.springframework.integration.core.MessageSelector;
|
||||
import org.springframework.integration.dsl.support.FixedSubscriberChannelPrototype;
|
||||
import org.springframework.integration.dsl.support.MessageChannelReference;
|
||||
import org.springframework.integration.expression.ControlBusMethodFilter;
|
||||
import org.springframework.integration.expression.FunctionExpression;
|
||||
import org.springframework.integration.filter.ExpressionEvaluatingSelector;
|
||||
import org.springframework.integration.filter.MessageFilter;
|
||||
@@ -60,8 +59,8 @@ import org.springframework.integration.filter.MethodInvokingSelector;
|
||||
import org.springframework.integration.handler.AbstractMessageProducingHandler;
|
||||
import org.springframework.integration.handler.BeanNameMessageProcessor;
|
||||
import org.springframework.integration.handler.BridgeHandler;
|
||||
import org.springframework.integration.handler.ControlBusMessageProcessor;
|
||||
import org.springframework.integration.handler.DelayHandler;
|
||||
import org.springframework.integration.handler.ExpressionCommandMessageProcessor;
|
||||
import org.springframework.integration.handler.LambdaMessageProcessor;
|
||||
import org.springframework.integration.handler.LoggingHandler;
|
||||
import org.springframework.integration.handler.MessageProcessor;
|
||||
@@ -517,8 +516,33 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
|
||||
* Populate the {@code Control Bus} EI Pattern specific {@link MessageHandler} implementation
|
||||
* at the current {@link IntegrationFlow} chain position.
|
||||
* @return the current {@link BaseIntegrationFlowDefinition}.
|
||||
* @see ExpressionCommandMessageProcessor
|
||||
* @since 6.4
|
||||
* @see ControlBusMessageProcessor
|
||||
*/
|
||||
public B controlBusOnRegistry() {
|
||||
return controlBusOnRegistry(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the {@code Control Bus} EI Pattern specific {@link MessageHandler} implementation
|
||||
* at the current {@link IntegrationFlow} chain position.
|
||||
* @param endpointConfigurer the {@link Consumer} to accept integration endpoint options.
|
||||
* @return the current {@link BaseIntegrationFlowDefinition}.
|
||||
* @since 6.4
|
||||
* @see GenericEndpointSpec
|
||||
* @see ControlBusMessageProcessor
|
||||
*/
|
||||
public B controlBusOnRegistry(@Nullable Consumer<GenericEndpointSpec<ServiceActivatingHandler>> endpointConfigurer) {
|
||||
return handle(new ServiceActivatingHandler(new ControlBusMessageProcessor()), endpointConfigurer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the {@code Control Bus} EI Pattern specific {@link MessageHandler} implementation
|
||||
* at the current {@link IntegrationFlow} chain position.
|
||||
* @return the current {@link BaseIntegrationFlowDefinition}.
|
||||
* @deprecated in favor of {@link #controlBusOnRegistry()}
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
public B controlBus() {
|
||||
return controlBus(null);
|
||||
}
|
||||
@@ -528,12 +552,15 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
|
||||
* at the current {@link IntegrationFlow} chain position.
|
||||
* @param endpointConfigurer the {@link Consumer} to accept integration endpoint options.
|
||||
* @return the current {@link BaseIntegrationFlowDefinition}.
|
||||
* @see ExpressionCommandMessageProcessor
|
||||
* @deprecated in favor of {@link #controlBusOnRegistry(Consumer)}
|
||||
* @see GenericEndpointSpec
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public B controlBus(@Nullable Consumer<GenericEndpointSpec<ServiceActivatingHandler>> endpointConfigurer) {
|
||||
return handle(new ServiceActivatingHandler(new ExpressionCommandMessageProcessor(
|
||||
new ControlBusMethodFilter())), endpointConfigurer);
|
||||
return handle(new ServiceActivatingHandler(
|
||||
new org.springframework.integration.handler.ExpressionCommandMessageProcessor(
|
||||
new org.springframework.integration.expression.ControlBusMethodFilter())), endpointConfigurer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2024 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.
|
||||
@@ -21,9 +21,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.core.annotation.AnnotationFilter;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.annotation.RepeatableContainers;
|
||||
import org.springframework.expression.MethodFilter;
|
||||
import org.springframework.integration.core.Pausable;
|
||||
import org.springframework.jmx.export.annotation.ManagedAttribute;
|
||||
@@ -45,9 +42,15 @@ import org.springframework.util.ReflectionUtils;
|
||||
* @author Gary Russell
|
||||
*
|
||||
* @since 4.0
|
||||
*
|
||||
* @deprecated in favor of {@link org.springframework.integration.support.management.ControlBusMethodFilter}
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
public class ControlBusMethodFilter implements MethodFilter {
|
||||
|
||||
private static final ReflectionUtils.MethodFilter CONTROL_BUS_METHOD_FILTER =
|
||||
new org.springframework.integration.support.management.ControlBusMethodFilter();
|
||||
|
||||
@Override
|
||||
public List<Method> filter(List<Method> methods) {
|
||||
List<Method> supportedMethods = new ArrayList<>();
|
||||
@@ -60,26 +63,7 @@ public class ControlBusMethodFilter implements MethodFilter {
|
||||
}
|
||||
|
||||
private boolean accept(Method method) {
|
||||
Class<?> declaringClass = method.getDeclaringClass();
|
||||
String methodName = method.getName();
|
||||
if ((Pausable.class.isAssignableFrom(declaringClass) || Lifecycle.class.isAssignableFrom(declaringClass))
|
||||
&& ReflectionUtils.findMethod(Pausable.class, methodName, method.getParameterTypes()) != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (CustomizableThreadCreator.class.isAssignableFrom(declaringClass)
|
||||
&& (methodName.startsWith("get")
|
||||
|| methodName.startsWith("set")
|
||||
|| methodName.startsWith("shutdown"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MergedAnnotations mergedAnnotations =
|
||||
MergedAnnotations.from(method, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY,
|
||||
RepeatableContainers.none(), AnnotationFilter.PLAIN);
|
||||
|
||||
return mergedAnnotations.get(ManagedAttribute.class).isPresent()
|
||||
|| mergedAnnotations.get(ManagedOperation.class).isPresent();
|
||||
return CONTROL_BUS_METHOD_FILTER.matches(method);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -58,6 +58,10 @@ public abstract class AbstractMessageProcessingSelector
|
||||
}
|
||||
}
|
||||
|
||||
protected MessageProcessor<Boolean> getMessageProcessor() {
|
||||
return this.messageProcessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean accept(Message<?> message) {
|
||||
Object result = this.messageProcessor.processMessage(message);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -33,6 +33,8 @@ import org.springframework.integration.handler.ExpressionEvaluatingMessageProces
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*
|
||||
* @see SimpleExpressionEvaluatingSelector
|
||||
*/
|
||||
public class ExpressionEvaluatingSelector extends AbstractMessageProcessingSelector {
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2024 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
|
||||
*
|
||||
* https://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.filter;
|
||||
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.integration.core.MessageSelector} implementation that
|
||||
* evaluates a simple SpEL expression - relies on the
|
||||
* {@link org.springframework.expression.spel.support.SimpleEvaluationContext}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 6.4
|
||||
*
|
||||
* @see ExpressionEvaluatingSelector
|
||||
*/
|
||||
public class SimpleExpressionEvaluatingSelector extends AbstractMessageProcessingSelector {
|
||||
|
||||
private final String expressionString;
|
||||
|
||||
public SimpleExpressionEvaluatingSelector(String expressionString) {
|
||||
super(new ExpressionEvaluatingMessageProcessor<>(expressionString, Boolean.class));
|
||||
((ExpressionEvaluatingMessageProcessor<?>) getMessageProcessor()).setSimpleEvaluationContext(true);
|
||||
this.expressionString = expressionString;
|
||||
}
|
||||
|
||||
public SimpleExpressionEvaluatingSelector(Expression expression) {
|
||||
super(new ExpressionEvaluatingMessageProcessor<>(expression, Boolean.class));
|
||||
((ExpressionEvaluatingMessageProcessor<?>) getMessageProcessor()).setSimpleEvaluationContext(true);
|
||||
this.expressionString = expression.getExpressionString();
|
||||
}
|
||||
|
||||
public String getExpressionString() {
|
||||
return this.expressionString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SimpleExpressionEvaluatingSelector for: [" + this.expressionString + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,8 +35,10 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
@@ -614,6 +616,14 @@ public abstract class AbstractMessageProducingHandler extends AbstractMessageHan
|
||||
if (processor instanceof BeanFactoryAware beanFactoryAware && beanFactory != null) {
|
||||
beanFactoryAware.setBeanFactory(beanFactory);
|
||||
}
|
||||
if (processor instanceof InitializingBean initializingBean) {
|
||||
try {
|
||||
initializingBean.afterPropertiesSet();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new BeanCreationException("Cannot initialize processor for: " + this, ex);
|
||||
}
|
||||
}
|
||||
if (!this.async && processor instanceof MethodInvokingMessageProcessor<?> methodInvokingMessageProcessor) {
|
||||
this.async = methodInvokingMessageProcessor.isAsync();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2024 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
|
||||
*
|
||||
* https://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.handler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.IntegrationMessageHeaderAccessor;
|
||||
import org.springframework.integration.IntegrationPattern;
|
||||
import org.springframework.integration.IntegrationPatternType;
|
||||
import org.springframework.integration.support.management.ControlBusCommandRegistry;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* A MessageProcessor implementation that expects a Control Bus command as a request message.
|
||||
* When processing, it evaluates a SpEL expression associated with requested command,
|
||||
* essentially target bean method invocation.
|
||||
* <p>
|
||||
* The arguments for the command must be provided
|
||||
* in the {@link IntegrationMessageHeaderAccessor#CONTROL_BUS_ARGUMENTS} message header.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 6.4
|
||||
*/
|
||||
public class ControlBusMessageProcessor extends AbstractMessageProcessor<Object>
|
||||
implements IntegrationPattern {
|
||||
|
||||
private ControlBusCommandRegistry controlBusCommandRegistry;
|
||||
|
||||
public ControlBusMessageProcessor() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance based on the provided {@link ControlBusCommandRegistry}.
|
||||
* @param controlBusCommandRegistry the {@link ControlBusCommandRegistry} with commands to execute.
|
||||
*/
|
||||
public ControlBusMessageProcessor(ControlBusCommandRegistry controlBusCommandRegistry) {
|
||||
this.controlBusCommandRegistry = controlBusCommandRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntegrationPatternType getIntegrationPatternType() {
|
||||
return IntegrationPatternType.control_bus;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
super.onInit();
|
||||
if (this.controlBusCommandRegistry == null) {
|
||||
this.controlBusCommandRegistry = getBeanFactory().getBean(ControlBusCommandRegistry.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object processMessage(Message<?> message) {
|
||||
String command = message.getPayload().toString();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Object> arguments =
|
||||
message.getHeaders().get(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.class);
|
||||
Class<?>[] parameterTypes = new Class<?>[0];
|
||||
if (!CollectionUtils.isEmpty(arguments)) {
|
||||
parameterTypes =
|
||||
arguments.stream()
|
||||
.map(Object::getClass)
|
||||
.toArray(Class<?>[]::new);
|
||||
}
|
||||
Expression commandExpression = this.controlBusCommandRegistry.getExpressionForCommand(command, parameterTypes);
|
||||
return evaluateExpression(commandExpression, arguments);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import org.springframework.expression.MethodExecutor;
|
||||
import org.springframework.expression.MethodFilter;
|
||||
import org.springframework.expression.MethodResolver;
|
||||
import org.springframework.expression.spel.support.ReflectiveMethodResolver;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.integration.IntegrationPattern;
|
||||
import org.springframework.integration.IntegrationPatternType;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -47,7 +48,10 @@ import org.springframework.util.CollectionUtils;
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*
|
||||
* @deprecated in favor of {@link ControlBusMessageProcessor}
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
public class ExpressionCommandMessageProcessor extends AbstractMessageProcessor<Object>
|
||||
implements IntegrationPattern {
|
||||
|
||||
@@ -74,7 +78,12 @@ public class ExpressionCommandMessageProcessor extends AbstractMessageProcessor<
|
||||
super.setBeanFactory(beanFactory);
|
||||
if (this.methodFilter != null) {
|
||||
MethodResolver methodResolver = new ExpressionCommandMethodResolver(this.methodFilter);
|
||||
getEvaluationContext().setMethodResolvers(Collections.singletonList(methodResolver));
|
||||
if (getEvaluationContext() instanceof StandardEvaluationContext standardEvaluationContext) {
|
||||
standardEvaluationContext.setMethodResolvers(Collections.singletonList(methodResolver));
|
||||
}
|
||||
else {
|
||||
logger.warn("Cannot customize the 'SimpleEvaluationContext'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.core.convert.ConverterNotFoundException;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
@@ -419,27 +420,30 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator im
|
||||
}
|
||||
|
||||
private void prepareEvaluationContext() {
|
||||
StandardEvaluationContext context = getEvaluationContext();
|
||||
Class<?> targetType = AopUtils.getTargetClass(this.targetObject);
|
||||
if (this.method != null) {
|
||||
context.registerMethodFilter(targetType,
|
||||
new FixedMethodFilter(ClassUtils.getMostSpecificMethod(this.method, targetType)));
|
||||
if (this.expectedType != null) {
|
||||
Assert.state(context.getTypeConverter()
|
||||
.canConvert(TypeDescriptor.valueOf((this.method).getReturnType()), this.expectedType),
|
||||
() -> "Cannot convert to expected type (" + this.expectedType + ") from " + this.method);
|
||||
EvaluationContext context = getEvaluationContext();
|
||||
if (context instanceof StandardEvaluationContext standardEvaluationContext) {
|
||||
Class<?> targetType = AopUtils.getTargetClass(this.targetObject);
|
||||
if (this.method != null) {
|
||||
standardEvaluationContext.registerMethodFilter(targetType,
|
||||
new FixedMethodFilter(ClassUtils.getMostSpecificMethod(this.method, targetType)));
|
||||
if (this.expectedType != null) {
|
||||
Assert.state(context.getTypeConverter()
|
||||
.canConvert(TypeDescriptor.valueOf((this.method).getReturnType()), this.expectedType),
|
||||
() -> "Cannot convert to expected type (" + this.expectedType + ") from " + this.method);
|
||||
}
|
||||
}
|
||||
else {
|
||||
AnnotatedMethodFilter filter = new AnnotatedMethodFilter(this.annotationType, this.methodName,
|
||||
this.requiresReply);
|
||||
Assert.state(canReturnExpectedType(filter, targetType, context.getTypeConverter()),
|
||||
() -> "Cannot convert to expected type (" + this.expectedType + ") from " + this.methodName);
|
||||
standardEvaluationContext.registerMethodFilter(targetType, filter);
|
||||
}
|
||||
}
|
||||
else {
|
||||
AnnotatedMethodFilter filter = new AnnotatedMethodFilter(this.annotationType, this.methodName,
|
||||
this.requiresReply);
|
||||
Assert.state(canReturnExpectedType(filter, targetType, context.getTypeConverter()),
|
||||
() -> "Cannot convert to expected type (" + this.expectedType + ") from " + this.methodName);
|
||||
context.registerMethodFilter(targetType, filter);
|
||||
}
|
||||
|
||||
context.setVariable("target", this.targetObject);
|
||||
try {
|
||||
context.registerFunction("requiredHeader",
|
||||
context.setVariable("requiredHeader",
|
||||
ParametersWrapper.class.getDeclaredMethod("getHeader", Map.class, String.class));
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.integration.IntegrationPatternType;
|
||||
import org.springframework.integration.core.MessageSelector;
|
||||
import org.springframework.integration.filter.ExpressionEvaluatingSelector;
|
||||
import org.springframework.integration.filter.SimpleExpressionEvaluatingSelector;
|
||||
import org.springframework.jmx.export.annotation.ManagedAttribute;
|
||||
import org.springframework.jmx.export.annotation.ManagedOperation;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -135,8 +136,8 @@ public class RecipientListRouter extends AbstractMessageRouter implements Recipi
|
||||
private void addRecipient(String channelName, String selectorExpression, Queue<Recipient> recipientsToAdd) {
|
||||
Assert.hasText(channelName, "'channelName' must not be empty.");
|
||||
Assert.hasText(selectorExpression, "'selectorExpression' must not be empty.");
|
||||
ExpressionEvaluatingSelector expressionEvaluatingSelector =
|
||||
new ExpressionEvaluatingSelector(selectorExpression);
|
||||
SimpleExpressionEvaluatingSelector expressionEvaluatingSelector =
|
||||
new SimpleExpressionEvaluatingSelector(selectorExpression);
|
||||
expressionEvaluatingSelector.setBeanFactory(getBeanFactory());
|
||||
Recipient recipient = new Recipient(channelName, expressionEvaluatingSelector);
|
||||
setupRecipient(recipient);
|
||||
@@ -203,9 +204,13 @@ public class RecipientListRouter extends AbstractMessageRouter implements Recipi
|
||||
Recipient next = it.next();
|
||||
MessageSelector selector = next.getSelector();
|
||||
MessageChannel channel = next.getChannel();
|
||||
if (selector instanceof ExpressionEvaluatingSelector
|
||||
if ((selector instanceof ExpressionEvaluatingSelector expressionEvaluatingSelector
|
||||
&& targetChannel.equals(channel)
|
||||
&& ((ExpressionEvaluatingSelector) selector).getExpressionString().equals(selectorExpression)) {
|
||||
&& expressionEvaluatingSelector.getExpressionString().equals(selectorExpression)) ||
|
||||
(selector instanceof SimpleExpressionEvaluatingSelector simpleExpressionEvaluatingSelector
|
||||
&& targetChannel.equals(channel)
|
||||
&& simpleExpressionEvaluatingSelector.getExpressionString().equals(selectorExpression))) {
|
||||
|
||||
it.remove();
|
||||
counter++;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2024 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.
|
||||
@@ -17,9 +17,11 @@
|
||||
package org.springframework.integration.router;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.integration.core.MessageSelector;
|
||||
import org.springframework.integration.support.management.IntegrationManagedResource;
|
||||
import org.springframework.jmx.export.annotation.ManagedAttribute;
|
||||
import org.springframework.jmx.export.annotation.ManagedOperation;
|
||||
@@ -31,6 +33,8 @@ import org.springframework.jmx.export.annotation.ManagedResource;
|
||||
*
|
||||
* @author Liujiong
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 4.1
|
||||
*
|
||||
*/
|
||||
@@ -40,6 +44,10 @@ public interface RecipientListRouterManagement {
|
||||
|
||||
/**
|
||||
* Add a recipient with channelName and expression.
|
||||
* The expression follows only
|
||||
* {@link org.springframework.expression.spel.support.SimpleEvaluationContext#forReadOnlyDataBinding()}
|
||||
* capabilities. Otherwise, use non-managed {@link RecipientListRouter#addRecipient(String, MessageSelector)}
|
||||
* API with more control over execution.
|
||||
* @param channelName The channel name.
|
||||
* @param selectorExpression The expression to filter the incoming message.
|
||||
*/
|
||||
@@ -78,6 +86,10 @@ public interface RecipientListRouterManagement {
|
||||
|
||||
/**
|
||||
* Replace recipient.
|
||||
* The expression follows only
|
||||
* {@link org.springframework.expression.spel.support.SimpleEvaluationContext#forReadOnlyDataBinding()}
|
||||
* capabilities. Otherwise, use non-managed {@link RecipientListRouter#addRecipient(String, MessageSelector)}
|
||||
* API with more control over execution.
|
||||
* @param recipientMappings contain channelName and expression.
|
||||
*/
|
||||
@ManagedOperation
|
||||
@@ -85,6 +97,10 @@ public interface RecipientListRouterManagement {
|
||||
|
||||
/**
|
||||
* Set recipients.
|
||||
* The expression follows only
|
||||
* {@link org.springframework.expression.spel.support.SimpleEvaluationContext#forReadOnlyDataBinding()}
|
||||
* capabilities. Otherwise, use non-managed {@link RecipientListRouter#setRecipients(List)}
|
||||
* API with more control over execution.
|
||||
* @param recipientMappings contain channelName and expression.
|
||||
*/
|
||||
@ManagedAttribute
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* Copyright 2024 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
|
||||
*
|
||||
* https://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.support.management;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.core.MethodIntrospector;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
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.ClassUtils;
|
||||
import org.springframework.util.CustomizableThreadCreator;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A global component to serve Control Bus command and respective SpEL expression relationships.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 6.4
|
||||
*
|
||||
* @see ControlBusMethodFilter
|
||||
*/
|
||||
public class ControlBusCommandRegistry
|
||||
implements ApplicationContextAware, SmartInitializingSingleton, DestructionAwareBeanPostProcessor {
|
||||
|
||||
private static final Pattern COMMAND_PATTERN =
|
||||
Pattern.compile("^@?'?(?<beanName>.[^']+)'?\\.(?<methodName>[a-zA-Z0-9_]+)\\(?\\)?$");
|
||||
|
||||
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
|
||||
|
||||
private static final ControlBusMethodFilter CONTROL_BUS_METHOD_FILTER = new ControlBusMethodFilter();
|
||||
|
||||
private final Map<String, Map<CommandMethod, Expression>> controlBusCommands = new HashMap<>();
|
||||
|
||||
private boolean eagerInitialization;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Set to {@code true} to turn on Control Bus commands loading after application context initialization.
|
||||
* @param eagerInitialization true to initialize this registry eagerly.
|
||||
*/
|
||||
public void setEagerInitialization(boolean eagerInitialization) {
|
||||
this.eagerInitialization = eagerInitialization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSingletonsInstantiated() {
|
||||
if (this.eagerInitialization) {
|
||||
this.applicationContext.getBeansOfType(null)
|
||||
.forEach(this::registerControlBusCommands);
|
||||
this.initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
// Only for dynamically created beans. Others are registered by afterSingletonsInstantiated()
|
||||
if (this.initialized) {
|
||||
registerControlBusCommands(beanName, bean);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually register Control Bus commands (if any) for a specific bean.
|
||||
* The bean must be in instance of {@link Lifecycle}, or {@link CustomizableThreadCreator},
|
||||
* or marked with the {@link ManagedResource}, or {@link IntegrationManagedResource} annotation.
|
||||
* @param beanName the bean name for registration
|
||||
* @param bean the bean for registration
|
||||
*/
|
||||
public void registerControlBusCommands(String beanName, Object bean) {
|
||||
Class<?> beanClass = bean.getClass();
|
||||
if (bean instanceof Lifecycle || bean instanceof CustomizableThreadCreator
|
||||
|| AnnotationUtils.findAnnotation(beanClass, ManagedResource.class) != null
|
||||
|| AnnotationUtils.findAnnotation(beanClass, IntegrationManagedResource.class) != null) {
|
||||
|
||||
ReflectionUtils.doWithMethods(beanClass, method -> populateExpressionForCommand(beanName, method),
|
||||
CONTROL_BUS_METHOD_FILTER);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
|
||||
this.controlBusCommands.remove(beanName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return registered Control Bus commands.
|
||||
* @return registered commands.
|
||||
*/
|
||||
public Map<String, Map<CommandMethod, String>> getCommands() {
|
||||
Map<String, Map<CommandMethod, String>> commands = new HashMap<>(this.controlBusCommands.size());
|
||||
for (Map.Entry<String, Map<CommandMethod, Expression>> beanEntry : this.controlBusCommands.entrySet()) {
|
||||
Map<CommandMethod, String> commandEntries =
|
||||
beanEntry.getValue()
|
||||
.entrySet()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey,
|
||||
(commandEntry) -> commandEntry.getValue().getExpressionString()));
|
||||
commands.put(beanEntry.getKey(), commandEntries);
|
||||
}
|
||||
return Collections.unmodifiableMap(commands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a SpEL expression for the command to call with parameter types.
|
||||
* The command must be in format {@code beanName.methodName}.
|
||||
* (Or {@code @beanName.methodName()} for backward compatibility with simple expressions.)
|
||||
* If {@code beanName} is a complex literal, it has to be wrapped into single quotes,
|
||||
* e.g. {@code 'some.complex.bean-name'}.
|
||||
* The target method to call must fit into the {@link ControlBusMethodFilter} requirements
|
||||
* and match with the provided parameter types.
|
||||
* @param command the command to call.
|
||||
* @param parameterTypes the parameter types for the target method to call.
|
||||
* @return the SpEL expression for the provided command.
|
||||
* @throws IllegalArgumentException if provided command does not match to Control Bus requirements
|
||||
* or target method contract.
|
||||
*/
|
||||
public Expression getExpressionForCommand(String command, Class<?>... parameterTypes) {
|
||||
Matcher matcher = COMMAND_PATTERN.matcher(command);
|
||||
Assert.isTrue(matcher.matches(),
|
||||
"The command must be in format 'beanName.methodName'. " +
|
||||
"Arguments must go to the 'controlBusArguments' message header.");
|
||||
|
||||
String beanName = matcher.group("beanName");
|
||||
Assert.isTrue(this.applicationContext.containsBean(beanName),
|
||||
() -> "There is no registered bean for requested command: " + beanName);
|
||||
|
||||
String methodName = matcher.group("methodName");
|
||||
|
||||
CommandMethod commandMethod = new CommandMethod(beanName, methodName, parameterTypes);
|
||||
return populateCommandMethod(commandMethod, (key) -> buildExpressionForMethodToCall(commandMethod));
|
||||
}
|
||||
|
||||
private void populateExpressionForCommand(String beanName, Method methodForCommand) {
|
||||
CommandMethod commandMethod =
|
||||
new CommandMethod(beanName, methodForCommand.getName(), methodForCommand.getParameterTypes());
|
||||
populateCommandMethod(commandMethod, (key) -> buildExpressionForMethodToCall(commandMethod, methodForCommand));
|
||||
}
|
||||
|
||||
private Expression populateCommandMethod(CommandMethod commandMethod,
|
||||
Function<CommandMethod, Expression> mappingFunction) {
|
||||
|
||||
String beanName = commandMethod.beanName;
|
||||
|
||||
Map<CommandMethod, Expression> beanControlBusCommands =
|
||||
this.controlBusCommands.computeIfAbsent(beanName, (key) -> new HashMap<>());
|
||||
|
||||
try {
|
||||
return beanControlBusCommands.computeIfAbsent(commandMethod, mappingFunction);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
if (beanControlBusCommands.isEmpty()) {
|
||||
this.controlBusCommands.remove(beanName);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private Expression buildExpressionForMethodToCall(CommandMethod commandMethod) {
|
||||
Object bean = this.applicationContext.getBean(commandMethod.beanName);
|
||||
Set<Method> candidates = MethodIntrospector.selectMethods(bean.getClass(),
|
||||
(ReflectionUtils.MethodFilter) method -> commandMethod.methodName.equals(method.getName()));
|
||||
|
||||
Optional<Method> methodForCommand =
|
||||
candidates.stream()
|
||||
.filter(method ->
|
||||
areParameterTypesEqual(method.getParameterTypes(), commandMethod.parameterTypes))
|
||||
.findFirst();
|
||||
|
||||
Assert.isTrue(methodForCommand.isPresent(),
|
||||
() -> "No method '%s' found in bean '%s' for parameter types '%s'"
|
||||
.formatted(commandMethod.methodName, bean, Arrays.toString(commandMethod.parameterTypes)));
|
||||
|
||||
return buildExpressionForMethodToCall(commandMethod, methodForCommand.get());
|
||||
}
|
||||
|
||||
private static Expression buildExpressionForMethodToCall(CommandMethod commandMethod, Method methodForCommand) {
|
||||
Assert.isTrue(CONTROL_BUS_METHOD_FILTER.matches(methodForCommand),
|
||||
() -> "The method '%s' is not valid Control Bus command".formatted(methodForCommand));
|
||||
|
||||
populateDescriptionIntoCommand(commandMethod, methodForCommand);
|
||||
|
||||
Class<?>[] parameterTypes = methodForCommand.getParameterTypes();
|
||||
StringBuilder expressionBuilder =
|
||||
new StringBuilder("@'")
|
||||
.append(commandMethod.beanName)
|
||||
.append("'.")
|
||||
.append(methodForCommand.getName())
|
||||
.append('(');
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
expressionBuilder.append('[').append(i).append("],");
|
||||
}
|
||||
if (parameterTypes.length > 0) {
|
||||
expressionBuilder.deleteCharAt(expressionBuilder.length() - 1);
|
||||
}
|
||||
String expression = expressionBuilder.append(')').toString();
|
||||
return EXPRESSION_PARSER.parseExpression(expression);
|
||||
}
|
||||
|
||||
private static boolean areParameterTypesEqual(Class<?>[] lhsTypes, Class<?>[] rhsTypes) {
|
||||
if (lhsTypes.length == rhsTypes.length) {
|
||||
for (int i = 0; i < lhsTypes.length; i++) {
|
||||
if (!ClassUtils.isAssignable(lhsTypes[i], rhsTypes[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void populateDescriptionIntoCommand(CommandMethod commandMethod, Method methodForCommand) {
|
||||
ManagedOperation managedOperation = AnnotationUtils.findAnnotation(methodForCommand, ManagedOperation.class);
|
||||
if (managedOperation != null) {
|
||||
commandMethod.description = managedOperation.description();
|
||||
}
|
||||
else {
|
||||
ManagedAttribute managedAttribute = AnnotationUtils.findAnnotation(methodForCommand, ManagedAttribute.class);
|
||||
if (managedAttribute != null) {
|
||||
commandMethod.description = managedAttribute.description();
|
||||
}
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(commandMethod.description)) {
|
||||
commandMethod.description = commandMethod.methodName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Java Bean to represent a Control Bus command as a bean method with its parameter types.
|
||||
*/
|
||||
public static final class CommandMethod {
|
||||
|
||||
private final String beanName;
|
||||
|
||||
private final String methodName;
|
||||
|
||||
private final Class<?>[] parameterTypes;
|
||||
|
||||
private String description;
|
||||
|
||||
private CommandMethod(String beanName, String methodName, Class<?>[] parameterTypes) {
|
||||
this.beanName = beanName;
|
||||
this.methodName = methodName;
|
||||
this.parameterTypes = parameterTypes;
|
||||
}
|
||||
|
||||
public String getBeanName() {
|
||||
return this.beanName;
|
||||
}
|
||||
|
||||
public String getMethodName() {
|
||||
return this.methodName;
|
||||
}
|
||||
|
||||
public Class<?>[] getParameterTypes() {
|
||||
return this.parameterTypes;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Objects.hash(this.beanName, this.methodName);
|
||||
result = 31 * result + Arrays.hashCode(this.parameterTypes);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
CommandMethod that = (CommandMethod) o;
|
||||
return Objects.equals(this.beanName, that.beanName)
|
||||
&& Objects.equals(this.methodName, that.methodName)
|
||||
&& areParameterTypesEqual(this.parameterTypes, that.parameterTypes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2014-2024 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
|
||||
*
|
||||
* https://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.support.management;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.core.annotation.AnnotationFilter;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.annotation.RepeatableContainers;
|
||||
import org.springframework.integration.core.Pausable;
|
||||
import org.springframework.jmx.export.annotation.ManagedAttribute;
|
||||
import org.springframework.jmx.export.annotation.ManagedOperation;
|
||||
import org.springframework.util.CustomizableThreadCreator;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* The {@link ReflectionUtils.MethodFilter} to restrict method invocations to:
|
||||
* <ul>
|
||||
* <li> {@link Pausable} or {@link Lifecycle} components
|
||||
* <li> {@code get}, {@code set} and {@code shutdown} methods of {@link CustomizableThreadCreator}
|
||||
* <li> methods with {@link ManagedAttribute} or {@link ManagedOperation} annotations
|
||||
* </ul>
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 6.4
|
||||
*/
|
||||
public class ControlBusMethodFilter implements ReflectionUtils.MethodFilter {
|
||||
|
||||
@Override
|
||||
public boolean matches(Method method) {
|
||||
if (Modifier.isPublic(method.getModifiers())) {
|
||||
Class<?> declaringClass = method.getDeclaringClass();
|
||||
String methodName = method.getName();
|
||||
if ((Pausable.class.isAssignableFrom(declaringClass) || Lifecycle.class.isAssignableFrom(declaringClass))
|
||||
&& ReflectionUtils.findMethod(Pausable.class, methodName, method.getParameterTypes()) != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (CustomizableThreadCreator.class.isAssignableFrom(declaringClass)
|
||||
&& (methodName.startsWith("get")
|
||||
|| methodName.startsWith("set")
|
||||
|| methodName.equals("shutdown"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MergedAnnotations mergedAnnotations =
|
||||
MergedAnnotations.from(method, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY,
|
||||
RepeatableContainers.none(), AnnotationFilter.PLAIN);
|
||||
|
||||
return mergedAnnotations.get(ManagedAttribute.class).isPresent()
|
||||
|| mergedAnnotations.get(ManagedOperation.class).isPresent();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -19,9 +19,9 @@ package org.springframework.integration.util;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
@@ -51,7 +51,9 @@ public abstract class AbstractExpressionEvaluator implements BeanFactoryAware, I
|
||||
|
||||
private final BeanFactoryTypeConverter typeConverter = new BeanFactoryTypeConverter();
|
||||
|
||||
private volatile StandardEvaluationContext evaluationContext;
|
||||
private boolean simpleEvaluationContext;
|
||||
|
||||
private volatile EvaluationContext evaluationContext;
|
||||
|
||||
private volatile BeanFactory beanFactory;
|
||||
|
||||
@@ -64,9 +66,6 @@ public abstract class AbstractExpressionEvaluator implements BeanFactoryAware, I
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
this.typeConverter.setBeanFactory(beanFactory);
|
||||
if (this.evaluationContext != null && this.evaluationContext.getBeanResolver() == null) {
|
||||
this.evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
|
||||
}
|
||||
}
|
||||
|
||||
protected BeanFactory getBeanFactory() {
|
||||
@@ -83,6 +82,17 @@ public abstract class AbstractExpressionEvaluator implements BeanFactoryAware, I
|
||||
return this.messageBuilderFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* The flag to indicate that a {@link org.springframework.expression.spel.support.SimpleEvaluationContext}
|
||||
* must be used for expression evaluations.
|
||||
* @param simpleEvaluationContext true to use the
|
||||
* {@link org.springframework.expression.spel.support.SimpleEvaluationContext}
|
||||
* @since 6.4
|
||||
*/
|
||||
public void setSimpleEvaluationContext(boolean simpleEvaluationContext) {
|
||||
this.simpleEvaluationContext = simpleEvaluationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void afterPropertiesSet() {
|
||||
getEvaluationContext();
|
||||
@@ -93,24 +103,21 @@ public abstract class AbstractExpressionEvaluator implements BeanFactoryAware, I
|
||||
onInit();
|
||||
}
|
||||
|
||||
protected StandardEvaluationContext getEvaluationContext() {
|
||||
protected EvaluationContext getEvaluationContext() {
|
||||
return getEvaluationContext(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a WARN log if the beanFactory field is null, unless the argument is false.
|
||||
* @param beanFactoryRequired set to false to suppress the warning.
|
||||
* @param beanFactoryRequired set to {@code false} to suppress the warning.
|
||||
* @return The evaluation context.
|
||||
*/
|
||||
protected final StandardEvaluationContext getEvaluationContext(boolean beanFactoryRequired) {
|
||||
protected final EvaluationContext getEvaluationContext(boolean beanFactoryRequired) {
|
||||
if (this.evaluationContext == null) {
|
||||
if (this.beanFactory == null && !beanFactoryRequired) {
|
||||
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext();
|
||||
this.evaluationContext = obtainEvaluationContext(beanFactoryRequired);
|
||||
if (this.evaluationContext instanceof StandardEvaluationContext standardEvaluationContext) {
|
||||
standardEvaluationContext.setTypeConverter(this.typeConverter);
|
||||
}
|
||||
else {
|
||||
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.beanFactory);
|
||||
}
|
||||
this.evaluationContext.setTypeConverter(this.typeConverter);
|
||||
if (this.beanFactory != null) {
|
||||
ConversionService conversionService = IntegrationUtils.getConversionService(this.beanFactory);
|
||||
if (conversionService != null) {
|
||||
@@ -121,6 +128,20 @@ public abstract class AbstractExpressionEvaluator implements BeanFactoryAware, I
|
||||
return this.evaluationContext;
|
||||
}
|
||||
|
||||
private EvaluationContext obtainEvaluationContext(boolean beanFactoryRequired) {
|
||||
if (this.beanFactory == null && !beanFactoryRequired) {
|
||||
return
|
||||
this.simpleEvaluationContext
|
||||
? ExpressionUtils.createSimpleEvaluationContext()
|
||||
: ExpressionUtils.createStandardEvaluationContext();
|
||||
}
|
||||
else {
|
||||
return this.simpleEvaluationContext
|
||||
? ExpressionUtils.createSimpleEvaluationContext(this.beanFactory)
|
||||
: ExpressionUtils.createStandardEvaluationContext(this.beanFactory);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected <T> T evaluateExpression(Expression expression, Message<?> message, @Nullable Class<T> expectedType) {
|
||||
try {
|
||||
|
||||
@@ -283,7 +283,19 @@ class KotlinIntegrationFlowDefinition(@PublishedApi internal val delegate: Integ
|
||||
/**
|
||||
* Populate the `Control Bus` EI Pattern specific [MessageHandler] implementation
|
||||
* at the current [IntegrationFlow] chain position.
|
||||
* @since 6.4
|
||||
*/
|
||||
fun controlBusOnRegistry(endpointConfigurer: GenericEndpointSpec<ServiceActivatingHandler>.() -> Unit = {}) {
|
||||
this.delegate.controlBusOnRegistry(endpointConfigurer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the `Control Bus` EI Pattern specific [MessageHandler] implementation
|
||||
* at the current [IntegrationFlow] chain position.
|
||||
*/
|
||||
@Deprecated("Use 'controlBusOnRegistry()' instead.",
|
||||
replaceWith = ReplaceWith("controlBusOnRegistry()"))
|
||||
@Suppress("DEPRECATION", "REMOVAL")
|
||||
fun controlBus(endpointConfigurer: GenericEndpointSpec<ServiceActivatingHandler>.() -> Unit = {}) {
|
||||
this.delegate.controlBus(endpointConfigurer)
|
||||
}
|
||||
|
||||
@@ -4968,6 +4968,16 @@ The list of component name patterns you want to track (e.g., tracked-components
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:attribute name="id" type="xsd:string"/>
|
||||
<xsd:attribute name="use-registry" type="xsd:boolean" default="false">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Set true to make Control Bus based on the global 'ControlBusCommandRegistry'
|
||||
which is a recommended way to configure Control Bus functionality.
|
||||
The false is by default for backward compatibility and is deprecated.
|
||||
This attributed will be true by default in the next major version and removed altogether eventually.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="retryAdviceType">
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
<beans:bean id="aggregatorBean"
|
||||
class="org.springframework.integration.config.TestAggregatorBean" />
|
||||
|
||||
<control-bus input-channel="controlBusChannel" output-channel="nullChannel"/>
|
||||
<control-bus input-channel="controlBusChannel" output-channel="nullChannel" use-registry="true"/>
|
||||
|
||||
</beans:beans>
|
||||
|
||||
@@ -16,19 +16,19 @@
|
||||
|
||||
package org.springframework.integration.config;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.integration.IntegrationMessageHeaderAccessor;
|
||||
import org.springframework.integration.store.MessageGroupStore;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -36,9 +36,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Dave Syer
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@DirtiesContext
|
||||
@SpringJUnitConfig
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
public class AggregatorWithMessageStoreParserTests {
|
||||
|
||||
@Autowired
|
||||
@@ -55,7 +54,6 @@ public class AggregatorWithMessageStoreParserTests {
|
||||
private MessageChannel controlBusChannel;
|
||||
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testAggregation() {
|
||||
input.send(createMessage("123", "id1", 3, 1, null));
|
||||
assertThat(messageGroupStore.getMessageGroup("id1").size()).isEqualTo(1);
|
||||
@@ -71,13 +69,15 @@ public class AggregatorWithMessageStoreParserTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testExpiry() {
|
||||
input.send(createMessage("123", "id1", 3, 1, null));
|
||||
assertThat(messageGroupStore.getMessageGroup("id1").size()).isEqualTo(1);
|
||||
input.send(createMessage("456", "id1", 3, 2, null));
|
||||
assertThat(messageGroupStore.getMessageGroup("id1").size()).isEqualTo(2);
|
||||
this.controlBusChannel.send(new GenericMessage<Object>("@messageStore.expireMessageGroups(-10000)"));
|
||||
this.controlBusChannel.send(
|
||||
MessageBuilder.withPayload("messageStore.expireMessageGroups")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of(-10000L))
|
||||
.build());
|
||||
assertThat(aggregatorBean
|
||||
.getAggregatedMessages().size()).as("One and only one message should have been aggregated")
|
||||
.isEqualTo(1);
|
||||
@@ -88,6 +88,7 @@ public class AggregatorWithMessageStoreParserTests {
|
||||
|
||||
private static <T> Message<T> createMessage(T payload, Object correlationId, int sequenceSize, int sequenceNumber,
|
||||
MessageChannel outputChannel) {
|
||||
|
||||
return MessageBuilder.withPayload(payload)
|
||||
.setCorrelationId(correlationId)
|
||||
.setSequenceSize(sequenceSize)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<queue />
|
||||
</channel>
|
||||
|
||||
<control-bus input-channel="input" output-channel="output">
|
||||
<control-bus input-channel="input" output-channel="output" use-registry="true">
|
||||
<poller fixed-rate="100" />
|
||||
</control-bus>
|
||||
|
||||
|
||||
@@ -16,17 +16,19 @@
|
||||
|
||||
package org.springframework.integration.config.xml;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.IntegrationMessageHeaderAccessor;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.jmx.export.annotation.ManagedOperation;
|
||||
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.junit4.SpringRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -36,7 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringJUnitConfig
|
||||
@DirtiesContext
|
||||
public class ControlBusExplicitPollerTests {
|
||||
|
||||
@@ -49,19 +51,18 @@ public class ControlBusExplicitPollerTests {
|
||||
@Test
|
||||
public void testDefaultEvaluationContext() {
|
||||
Message<?> message =
|
||||
MessageBuilder.withPayload("@service.convert('aardvark')+headers.foo")
|
||||
.setHeader("foo", "bar")
|
||||
MessageBuilder.withPayload("service.convert")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of("aardvark", "bar"))
|
||||
.build();
|
||||
this.input.send(message);
|
||||
assertThat(output.receive(1000).getPayload()).isEqualTo("catbar");
|
||||
assertThat(output.receive(0)).isNull();
|
||||
}
|
||||
|
||||
public static class Service {
|
||||
|
||||
@ManagedOperation
|
||||
public String convert(String input) {
|
||||
return "cat";
|
||||
public String convert(String input, String header) {
|
||||
return "cat" + header;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
|
||||
<control-bus input-channel="inputChannel" auto-startup="true"/>
|
||||
<control-bus input-channel="inputChannel" auto-startup="true" use-registry="true"/>
|
||||
|
||||
<inbound-channel-adapter id="adapter" channel="outputChannel" auto-startup="false" method="receive">
|
||||
<poller fixed-rate="1000"/>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<queue />
|
||||
</channel>
|
||||
|
||||
<control-bus input-channel="input" output-channel="output"/>
|
||||
<control-bus input-channel="input" output-channel="output" use-registry="true"/>
|
||||
|
||||
<poller default="true" fixed-rate="100"/>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 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,16 +16,18 @@
|
||||
|
||||
package org.springframework.integration.config.xml;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.IntegrationMessageHeaderAccessor;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
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.junit4.SpringRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -35,7 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringJUnitConfig
|
||||
@DirtiesContext
|
||||
public class ControlBusPollerTests {
|
||||
|
||||
@@ -48,18 +50,17 @@ public class ControlBusPollerTests {
|
||||
@Test
|
||||
public void testDefaultEvaluationContext() {
|
||||
Message<?> message =
|
||||
MessageBuilder.withPayload("@service.convert('aardvark')+headers.foo")
|
||||
.setHeader("foo", "bar")
|
||||
MessageBuilder.withPayload("service.convert")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of("aardvark", "bar"))
|
||||
.build();
|
||||
this.input.send(message);
|
||||
assertThat(output.receive(1000).getPayload()).isEqualTo("catbar");
|
||||
assertThat(output.receive(0)).isNull();
|
||||
}
|
||||
|
||||
public static class Service {
|
||||
|
||||
public String convert(String input) {
|
||||
return "cat";
|
||||
public String convert(String input, String header) {
|
||||
return "cat" + header;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<queue/>
|
||||
</channel>
|
||||
|
||||
<control-bus input-channel="input" output-channel="output" send-timeout="100" order="1" auto-startup="false"/>
|
||||
<control-bus input-channel="input" output-channel="output" send-timeout="100" order="1" auto-startup="false" use-registry="true"/>
|
||||
|
||||
<recipient-list-router id="simpleRouter" input-channel="routingChannelA"/>
|
||||
|
||||
|
||||
@@ -18,15 +18,17 @@ package org.springframework.integration.config.xml;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.integration.IntegrationMessageHeaderAccessor;
|
||||
import org.springframework.integration.core.MessagingTemplate;
|
||||
import org.springframework.integration.router.RecipientListRouter.Recipient;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
@@ -36,17 +38,17 @@ import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.annotation.DirtiesContext.ClassMode;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Liujiong
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringJUnitConfig
|
||||
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
public class ControlBusRecipientListRouterTests {
|
||||
|
||||
@@ -63,63 +65,79 @@ public class ControlBusRecipientListRouterTests {
|
||||
@Qualifier("routingChannelA")
|
||||
private MessageChannel channel;
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void aa() {
|
||||
context.start();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddRecipient() {
|
||||
MessagingTemplate messagingTemplate = new MessagingTemplate();
|
||||
messagingTemplate.setReceiveTimeout(1000);
|
||||
messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.addRecipient('channel2','true')");
|
||||
Message<?> message = new GenericMessage<Integer>(1);
|
||||
this.input.send(
|
||||
MessageBuilder.withPayload("'simpleRouter.handler'.addRecipient")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of("channel2", "true"))
|
||||
.build());
|
||||
Message<?> message = new GenericMessage<>(1);
|
||||
channel.send(message);
|
||||
PollableChannel chanel2 = (PollableChannel) context.getBean("channel2");
|
||||
assertThat(chanel2.receive(0).getPayload().equals(1)).isTrue();
|
||||
assertThat(chanel2.receive(0).getPayload()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddRecipientWithNullExpression() {
|
||||
MessagingTemplate messagingTemplate = new MessagingTemplate();
|
||||
messagingTemplate.setReceiveTimeout(1000);
|
||||
messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.addRecipient('channel3')");
|
||||
this.input.send(
|
||||
MessageBuilder.withPayload("'simpleRouter.handler'.addRecipient")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of("channel3"))
|
||||
.build());
|
||||
|
||||
Message<?> message = new GenericMessage<Integer>(1);
|
||||
Message<?> message = new GenericMessage<>(1);
|
||||
channel.send(message);
|
||||
PollableChannel chanel3 = (PollableChannel) context.getBean("channel3");
|
||||
assertThat(chanel3.receive(0).getPayload().equals(1)).isTrue();
|
||||
assertThat(chanel3.receive(0).getPayload()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveRecipient() {
|
||||
MessagingTemplate messagingTemplate = new MessagingTemplate();
|
||||
messagingTemplate.setReceiveTimeout(1000);
|
||||
messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.addRecipient('channel1')");
|
||||
messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.addRecipient('channel4')");
|
||||
messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.removeRecipient('channel4')");
|
||||
this.input.send(
|
||||
MessageBuilder.withPayload("'simpleRouter.handler'.addRecipient")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of("channel1"))
|
||||
.build());
|
||||
this.input.send(
|
||||
MessageBuilder.withPayload("'simpleRouter.handler'.addRecipient")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of("channel4"))
|
||||
.build());
|
||||
this.input.send(
|
||||
MessageBuilder.withPayload("'simpleRouter.handler'.removeRecipient")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of("channel4"))
|
||||
.build());
|
||||
|
||||
Message<?> message = new GenericMessage<Integer>(1);
|
||||
Message<?> message = new GenericMessage<>(1);
|
||||
channel.send(message);
|
||||
PollableChannel chanel1 = (PollableChannel) context.getBean("channel1");
|
||||
PollableChannel chanel4 = (PollableChannel) context.getBean("channel4");
|
||||
assertThat(chanel1.receive(0).getPayload().equals(1)).isTrue();
|
||||
assertThat(chanel1.receive(0).getPayload()).isEqualTo(1);
|
||||
assertThat(chanel4.receive(0)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveRecipientWithExpression() {
|
||||
MessagingTemplate messagingTemplate = new MessagingTemplate();
|
||||
messagingTemplate.setReceiveTimeout(1000);
|
||||
messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.addRecipient('channel1','true')");
|
||||
messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.addRecipient('channel5','true')");
|
||||
messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.removeRecipient('channel5','true')");
|
||||
this.input.send(
|
||||
MessageBuilder.withPayload("'simpleRouter.handler'.addRecipient")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of("channel1", "true"))
|
||||
.build());
|
||||
this.input.send(
|
||||
MessageBuilder.withPayload("'simpleRouter.handler'.addRecipient")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of("channel5", "true"))
|
||||
.build());
|
||||
this.input.send(
|
||||
MessageBuilder.withPayload("'simpleRouter.handler'.removeRecipient")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of("channel5", "true"))
|
||||
.build());
|
||||
|
||||
Message<?> message = new GenericMessage<Integer>(1);
|
||||
Message<?> message = new GenericMessage<>(1);
|
||||
channel.send(message);
|
||||
PollableChannel chanel1 = (PollableChannel) context.getBean("channel1");
|
||||
PollableChannel chanel5 = (PollableChannel) context.getBean("channel5");
|
||||
assertThat(chanel1.receive(0).getPayload().equals(1)).isTrue();
|
||||
assertThat(chanel1.receive(0).getPayload()).isEqualTo(1);
|
||||
assertThat(chanel5.receive(0)).isNull();
|
||||
}
|
||||
|
||||
@@ -127,8 +145,10 @@ public class ControlBusRecipientListRouterTests {
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testGetRecipients() {
|
||||
MessagingTemplate messagingTemplate = new MessagingTemplate();
|
||||
messagingTemplate.setReceiveTimeout(1000);
|
||||
messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.addRecipient('channel1')");
|
||||
messagingTemplate.send(input,
|
||||
MessageBuilder.withPayload("'simpleRouter.handler'.addRecipient")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of("channel1"))
|
||||
.build());
|
||||
messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.getRecipients()");
|
||||
PollableChannel channel1 = (PollableChannel) context.getBean("channel1");
|
||||
Message<?> result = this.output.receive(0);
|
||||
@@ -138,29 +158,31 @@ public class ControlBusRecipientListRouterTests {
|
||||
|
||||
@Test
|
||||
public void testSetRecipients() {
|
||||
MessagingTemplate messagingTemplate = new MessagingTemplate();
|
||||
messagingTemplate.setReceiveTimeout(1000);
|
||||
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("channel6", "true");
|
||||
Message<?> message = MessageBuilder.withPayload("@'simpleRouter.handler'.setRecipientMappings(headers.recipientMap)").setHeader("recipientMap", map).build();
|
||||
Message<?> message =
|
||||
MessageBuilder.withPayload("'simpleRouter.handler'.setRecipientMappings")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of(map))
|
||||
.build();
|
||||
this.input.send(message);
|
||||
message = new GenericMessage<Integer>(1);
|
||||
message = new GenericMessage<>(1);
|
||||
channel.send(message);
|
||||
PollableChannel chanel6 = (PollableChannel) context.getBean("channel6");
|
||||
assertThat(chanel6.receive(0).getPayload().equals(1)).isTrue();
|
||||
assertThat(chanel6.receive(0).getPayload()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceRecipients() {
|
||||
MessagingTemplate messagingTemplate = new MessagingTemplate();
|
||||
messagingTemplate.setReceiveTimeout(1000);
|
||||
|
||||
messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.replaceRecipients('channel7=true')");
|
||||
Message<?> message = new GenericMessage<Integer>(1);
|
||||
Properties newMapping = new Properties();
|
||||
newMapping.setProperty("channel7", "true");
|
||||
this.input.send(
|
||||
MessageBuilder.withPayload("'simpleRouter.handler'.replaceRecipients")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of(newMapping))
|
||||
.build());
|
||||
Message<?> message = new GenericMessage<>(1);
|
||||
channel.send(message);
|
||||
PollableChannel chanel7 = (PollableChannel) context.getBean("channel7");
|
||||
assertThat(chanel7.receive(0).getPayload().equals(1)).isTrue();
|
||||
assertThat(chanel7.receive(0).getPayload()).isEqualTo(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<queue/>
|
||||
</channel>
|
||||
|
||||
<control-bus input-channel="input" output-channel="output" send-timeout="100" order="1" auto-startup="true"/>
|
||||
<control-bus input-channel="input" output-channel="output" send-timeout="100" order="1" auto-startup="true" use-registry="true"/>
|
||||
|
||||
<beans:bean id="service" class="org.springframework.integration.config.xml.ControlBusTests$Service" />
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -17,7 +17,9 @@
|
||||
package org.springframework.integration.config.xml;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -26,6 +28,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.integration.IntegrationMessageHeaderAccessor;
|
||||
import org.springframework.integration.channel.DefaultHeaderChannelRegistry;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.core.MessagingTemplate;
|
||||
@@ -68,17 +71,19 @@ public class ControlBusTests {
|
||||
@Test
|
||||
public void testDefaultEvaluationContext() {
|
||||
Message<?> message =
|
||||
MessageBuilder.withPayload("@service.convert('aardvark')+headers.foo")
|
||||
.setHeader("foo", "bar")
|
||||
MessageBuilder.withPayload("service.convert")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of("aardvark", "bar"))
|
||||
.build();
|
||||
this.input.send(message);
|
||||
assertThat(output.receive(0).getPayload()).isEqualTo("catbar");
|
||||
assertThat(output.receive(0)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testvoidOperation() throws Exception {
|
||||
Message<?> message = MessageBuilder.withPayload("@service.voidOp('foo')").build();
|
||||
Message<?> message =
|
||||
MessageBuilder.withPayload("service.voidOp")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of("foo"))
|
||||
.build();
|
||||
this.input.send(message);
|
||||
assertThat(this.service.latch.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
}
|
||||
@@ -130,8 +135,13 @@ public class ControlBusTests {
|
||||
Map<?, ?> mappings = (Map<?, ?>) result.getPayload();
|
||||
assertThat(mappings.get("foo")).isEqualTo("bar");
|
||||
assertThat(mappings.get("baz")).isEqualTo("qux");
|
||||
messagingTemplate.convertAndSend(input,
|
||||
"@'router.handler'.replaceChannelMappings('foo=qux \n baz=bar')");
|
||||
Properties newMapping = new Properties();
|
||||
newMapping.setProperty("foo", "qux");
|
||||
newMapping.setProperty("baz", "bar");
|
||||
messagingTemplate.send(input,
|
||||
MessageBuilder.withPayload("'router.handler'.replaceChannelMappings")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of(newMapping))
|
||||
.build());
|
||||
messagingTemplate.convertAndSend(input, "@'router.handler'.getChannelMappings()");
|
||||
result = this.output.receive(0);
|
||||
assertThat(result).isNotNull();
|
||||
@@ -145,8 +155,8 @@ public class ControlBusTests {
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
@ManagedOperation
|
||||
public String convert(String input) {
|
||||
return "cat";
|
||||
public String convert(String input, String header) {
|
||||
return "cat" + header;
|
||||
}
|
||||
|
||||
@ManagedOperation
|
||||
|
||||
@@ -89,10 +89,10 @@ import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.NullChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.channel.interceptor.WireTap;
|
||||
import org.springframework.integration.config.ControlBusFactoryBean;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.integration.config.EnableMessageHistory;
|
||||
import org.springframework.integration.config.EnablePublisher;
|
||||
import org.springframework.integration.config.ExpressionControlBusFactoryBean;
|
||||
import org.springframework.integration.config.GlobalChannelInterceptor;
|
||||
import org.springframework.integration.config.IntegrationConverter;
|
||||
import org.springframework.integration.config.SpelFunctionFactoryBean;
|
||||
@@ -454,15 +454,15 @@ public class EnableIntegrationTests {
|
||||
assertThat(message.getHeaders().get("foo")).isEqualTo("FOO");
|
||||
|
||||
MessagingTemplate messagingTemplate = new MessagingTemplate(this.controlBusChannel);
|
||||
assertThat(messagingTemplate.convertSendAndReceive("@pausable.isRunning()", Boolean.class)).isEqualTo(false);
|
||||
this.controlBusChannel.send(new GenericMessage<>("@pausable.start()"));
|
||||
assertThat(messagingTemplate.convertSendAndReceive("@pausable.isRunning()", Boolean.class)).isEqualTo(true);
|
||||
this.controlBusChannel.send(new GenericMessage<>("@pausable.stop()"));
|
||||
assertThat(messagingTemplate.convertSendAndReceive("@pausable.isRunning()", Boolean.class)).isEqualTo(false);
|
||||
this.controlBusChannel.send(new GenericMessage<>("@pausable.pause()"));
|
||||
assertThat(messagingTemplate.convertSendAndReceive("pausable.isRunning", Boolean.class)).isEqualTo(false);
|
||||
this.controlBusChannel.send(new GenericMessage<>("pausable.start"));
|
||||
assertThat(messagingTemplate.convertSendAndReceive("pausable.isRunning", Boolean.class)).isEqualTo(true);
|
||||
this.controlBusChannel.send(new GenericMessage<>("pausable.stop"));
|
||||
assertThat(messagingTemplate.convertSendAndReceive("pausable.isRunning", Boolean.class)).isEqualTo(false);
|
||||
this.controlBusChannel.send(new GenericMessage<>("pausable.pause"));
|
||||
Object pausable = this.context.getBean("pausable");
|
||||
assertThat(TestUtils.getPropertyValue(pausable, "paused", Boolean.class)).isTrue();
|
||||
this.controlBusChannel.send(new GenericMessage<>("@pausable.resume()"));
|
||||
this.controlBusChannel.send(new GenericMessage<>("pausable.resume"));
|
||||
assertThat(TestUtils.getPropertyValue(pausable, "paused", Boolean.class)).isFalse();
|
||||
|
||||
Map<String, ServiceActivatingHandler> beansOfType =
|
||||
@@ -1113,8 +1113,8 @@ public class EnableIntegrationTests {
|
||||
@ServiceActivator(inputChannel = "controlBusChannel")
|
||||
@EndpointId("controlBusEndpoint")
|
||||
@Role("bar")
|
||||
public ExpressionControlBusFactoryBean controlBus() {
|
||||
return new ExpressionControlBusFactoryBean();
|
||||
public ControlBusFactoryBean controlBus() {
|
||||
return new ControlBusFactoryBean();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
|
||||
@@ -214,7 +214,7 @@ public class IntegrationFlowTests {
|
||||
.withCauseInstanceOf(MessageDispatchingException.class)
|
||||
.withMessageContaining("Dispatcher has no subscribers");
|
||||
|
||||
this.controlBus.send("@payloadSerializingTransformer.start()");
|
||||
this.controlBus.send("payloadSerializingTransformer.start");
|
||||
|
||||
final AtomicBoolean used = new AtomicBoolean();
|
||||
|
||||
@@ -253,7 +253,7 @@ public class IntegrationFlowTests {
|
||||
.withCauseInstanceOf(MessageDispatchingException.class)
|
||||
.withMessageContaining("Dispatcher has no subscribers");
|
||||
|
||||
this.controlBus.send("@bridge.start()");
|
||||
this.controlBus.send("bridge.start");
|
||||
this.bridgeFlow2Input.send(message);
|
||||
reply = this.bridgeFlow2Output.receive(10000);
|
||||
assertThat(reply).isNotNull();
|
||||
@@ -623,7 +623,7 @@ public class IntegrationFlowTests {
|
||||
@Bean
|
||||
public IntegrationFlow controlBusFlow() {
|
||||
return IntegrationFlow.from(ControlBusGateway.class, (gateway) -> gateway.beanName("controlBusGateway"))
|
||||
.controlBus((endpoint) -> endpoint.id("controlBus"))
|
||||
.controlBusOnRegistry((endpoint) -> endpoint.id("controlBus"))
|
||||
.get();
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ import org.springframework.integration.aop.ReceiveMessageAdvice;
|
||||
import org.springframework.integration.aop.SimpleActiveIdleReceiveMessageAdvice;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.NullChannel;
|
||||
import org.springframework.integration.config.ControlBusFactoryBean;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.integration.config.ExpressionControlBusFactoryBean;
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.integration.scheduling.PollSkipAdvice;
|
||||
import org.springframework.integration.scheduling.SimplePollSkipStrategy;
|
||||
@@ -163,9 +163,9 @@ public class PollerAdviceTests {
|
||||
|
||||
@Test
|
||||
public void testSkipSimpleControlBus() {
|
||||
this.control.send(new GenericMessage<>("@skipper.skipPolls()"));
|
||||
this.control.send(new GenericMessage<>("skipper.skipPolls"));
|
||||
assertThat(this.skipper.skipPoll()).isTrue();
|
||||
this.control.send(new GenericMessage<>("@skipper.reset()"));
|
||||
this.control.send(new GenericMessage<>("skipper.reset"));
|
||||
assertThat(this.skipper.skipPoll()).isFalse();
|
||||
}
|
||||
|
||||
@@ -444,8 +444,8 @@ public class PollerAdviceTests {
|
||||
|
||||
@Bean
|
||||
@ServiceActivator(inputChannel = "control")
|
||||
public ExpressionControlBusFactoryBean controlBus() {
|
||||
return new ExpressionControlBusFactoryBean();
|
||||
public ControlBusFactoryBean controlBus() {
|
||||
return new ControlBusFactoryBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
* Copyright 2015-2024 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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.integration.support.management;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -26,17 +27,25 @@ import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.integration.IntegrationMessageHeaderAccessor;
|
||||
import org.springframework.integration.channel.AbstractMessageChannel;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.integration.config.EnableIntegrationManagement;
|
||||
import org.springframework.integration.config.IntegrationManagementConfigurer;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.integration.endpoint.AbstractMessageSource;
|
||||
import org.springframework.integration.handler.AbstractMessageHandler;
|
||||
import org.springframework.integration.handler.ControlBusMessageProcessor;
|
||||
import org.springframework.integration.router.RecipientListRouter;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -53,7 +62,7 @@ public class IntegrationManagementConfigurerTests {
|
||||
public void testDefaults() {
|
||||
DirectChannel channel = new DirectChannel();
|
||||
AbstractMessageHandler handler = new RecipientListRouter();
|
||||
AbstractMessageSource<?> source = new AbstractMessageSource<Object>() {
|
||||
AbstractMessageSource<?> source = new AbstractMessageSource<>() {
|
||||
|
||||
@Override
|
||||
public String getComponentType() {
|
||||
@@ -94,6 +103,68 @@ public class IntegrationManagementConfigurerTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void controlBusIntegration() {
|
||||
try (ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(ControlBusEagerConfig.class)) {
|
||||
ControlBusCommandRegistry controlBusCommandRegistry =
|
||||
ctx.getBean(IntegrationContextUtils.CONTROL_BUS_COMMAND_REGISTRY_BEAN_NAME,
|
||||
ControlBusCommandRegistry.class);
|
||||
|
||||
Map<String, Map<ControlBusCommandRegistry.CommandMethod, String>> commands =
|
||||
controlBusCommandRegistry.getCommands();
|
||||
|
||||
assertThat(commands).containsKeys("errorChannel", "nullChannel", "taskScheduler", "channel",
|
||||
"_org.springframework.integration.errorLogger",
|
||||
"_org.springframework.integration.errorLogger.handler");
|
||||
|
||||
Map<ControlBusCommandRegistry.CommandMethod, String> commandMethodStringMap = commands.get("channel");
|
||||
|
||||
List<String> controlBusMethodForChannelBean =
|
||||
commandMethodStringMap.keySet()
|
||||
.stream()
|
||||
.map(ControlBusCommandRegistry.CommandMethod::getMethodName)
|
||||
.toList();
|
||||
|
||||
assertThat(controlBusMethodForChannelBean)
|
||||
.containsOnly("setLoggingEnabled", "isLoggingEnabled", "setShouldTrack");
|
||||
|
||||
Expression isLoggingEnabledCommand =
|
||||
controlBusCommandRegistry.getExpressionForCommand("nullChannel.isLoggingEnabled");
|
||||
|
||||
StandardEvaluationContext evaluationContext = IntegrationContextUtils.getEvaluationContext(ctx);
|
||||
|
||||
assertThat(isLoggingEnabledCommand.getValue(evaluationContext, boolean.class)).isFalse();
|
||||
|
||||
Expression setLoggingEnabledCommand =
|
||||
controlBusCommandRegistry.getExpressionForCommand("nullChannel.setLoggingEnabled", boolean.class);
|
||||
|
||||
setLoggingEnabledCommand.getValue(evaluationContext, new Object[] {true});
|
||||
|
||||
assertThat(isLoggingEnabledCommand.getValue(evaluationContext, boolean.class)).isTrue();
|
||||
|
||||
ControlBusMessageProcessor controlBusMessageProcessor = ctx.getBean(ControlBusMessageProcessor.class);
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() ->
|
||||
controlBusMessageProcessor.processMessage(new GenericMessage<>("nonSuchBean.command")))
|
||||
.withMessage("There is no registered bean for requested command: nonSuchBean");
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() ->
|
||||
controlBusMessageProcessor.processMessage(new GenericMessage<>("channel.noSuchCommand")))
|
||||
.withMessageStartingWith("No method 'noSuchCommand' found in bean 'bean 'channel'");
|
||||
|
||||
controlBusMessageProcessor.processMessage(
|
||||
MessageBuilder.withPayload("nullChannel.setLoggingEnabled")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CONTROL_BUS_ARGUMENTS, List.of(false))
|
||||
.build());
|
||||
|
||||
assertThat((Boolean) controlBusMessageProcessor.processMessage(
|
||||
new GenericMessage<>("nullChannel.isLoggingEnabled")))
|
||||
.isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
@EnableIntegrationManagement
|
||||
@@ -113,4 +184,21 @@ public class IntegrationManagementConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
@EnableIntegrationManagement(loadControlBusCommands = "true", defaultLoggingEnabled = "false")
|
||||
public static class ControlBusEagerConfig {
|
||||
|
||||
@Bean
|
||||
MessageChannel channel() {
|
||||
return new DirectChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ControlBusMessageProcessor controlBusMessageProcessor(ControlBusCommandRegistry controlBusCommandRegistry) {
|
||||
return new ControlBusMessageProcessor(controlBusCommandRegistry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user