INT-4457: Add logAndReply() terminal operator

JIRA: https://jira.spring.io/browse/INT-4457

When an `IntegrationFlow` is reply-based (we expect a reply in the
beginning) and `log()` (or `wireTap()`) is used in the end, we are
forced to add an empty `bridge()` in the end to ensure a `replyChannel`
header routing

* Fix `IntegrationFlowDefinition` to add `enrichHeaders()` in the end
to populate a `nullChannel` as a `replyChannel` header if that is missed
in the request message headers.
This way we cover both use-cases when we expect reply from the flow and
when it is used as a one-way scenario
* Improve a `HeaderEnricher` do not create a new `Message` if there are
no new headers to add/remove
* Remove a note from the `dsl.adoc` about now redundant `bridge()` after
`log()`
* Resolve TODO in the `.handle()` paragraph

* Restore anonymous class in the MessageHistoryIntegrationTests:
Mockito can't mock/spy lambdas because they are `final` classes

* Introduce `IntegrationFlowDefinition.logAndReply()` operator

* Fix logging message

Doc Polishing
This commit is contained in:
Artem Bilan
2018-08-02 16:36:35 -04:00
committed by Gary Russell
parent 4a85849bcc
commit 055e9a40db
7 changed files with 386 additions and 72 deletions

View File

@@ -2194,6 +2194,9 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
* logging level and {@code org.springframework.integration.handler.LoggingHandler}
* as a default logging category.
* <p> The full request {@link Message} will be logged.
* <p> When this operator is used in the end of flow, it is treated
* as one-way handler without any replies to continue.
* The {@link #logAndReply()} should be used for request-reply configuration.
* @return the current {@link IntegrationFlowDefinition}.
* @see #wireTap(WireTapSpec)
*/
@@ -2207,6 +2210,9 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
* logging level and {@code org.springframework.integration.handler.LoggingHandler}
* as a default logging category.
* <p> The full request {@link Message} will be logged.
* <p> When this operator is used in the end of flow, it is treated
* as one-way handler without any replies to continue.
* The {@link #logAndReply()} should be used for request-reply configuration.
* @param level the {@link LoggingHandler.Level}.
* @return the current {@link IntegrationFlowDefinition}.
* @see #wireTap(WireTapSpec)
@@ -2220,6 +2226,9 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
* with the {@link LoggingHandler} subscriber for the provided logging category
* and {@code INFO} logging level.
* <p> The full request {@link Message} will be logged.
* <p> When this operator is used in the end of flow, it is treated
* as one-way handler without any replies to continue.
* The {@link #logAndReply()} should be used for request-reply configuration.
* @param category the logging category to use.
* @return the current {@link IntegrationFlowDefinition}.
* @see #wireTap(WireTapSpec)
@@ -2233,6 +2242,9 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
* with the {@link LoggingHandler} subscriber for the provided
* {@link LoggingHandler.Level} logging level and logging category.
* <p> The full request {@link Message} will be logged.
* <p> When this operator is used in the end of flow, it is treated
* as one-way handler without any replies to continue.
* The {@link #logAndReply()} should be used for request-reply configuration.
* @param level the {@link LoggingHandler.Level}.
* @param category the logging category to use.
* @return the current {@link IntegrationFlowDefinition}.
@@ -2247,6 +2259,9 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
* with the {@link LoggingHandler} subscriber for the provided
* {@link LoggingHandler.Level} logging level, logging category
* and SpEL expression for the log message.
* <p> When this operator is used in the end of flow, it is treated
* as one-way handler without any replies to continue.
* The {@link #logAndReply()} should be used for request-reply configuration.
* @param level the {@link LoggingHandler.Level}.
* @param category the logging category.
* @param logExpression the SpEL expression to evaluate logger message at runtime
@@ -2264,6 +2279,9 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
* with the {@link LoggingHandler} subscriber for the {@code INFO} logging level,
* the {@code org.springframework.integration.handler.LoggingHandler}
* as a default logging category and {@link Function} for the log message.
* <p> When this operator is used in the end of flow, it is treated
* as one-way handler without any replies to continue.
* The {@link #logAndReply()} should be used for request-reply configuration.
* @param function the function to evaluate logger message at runtime
* @param <P> the expected payload type.
* against the request {@link Message}.
@@ -2281,6 +2299,9 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
* the {@code org.springframework.integration.handler.LoggingHandler}
* as a default logging category and SpEL expression to evaluate
* logger message at runtime against the request {@link Message}.
* <p> When this operator is used in the end of flow, it is treated
* as one-way handler without any replies to continue.
* The {@link #logAndReply()} should be used for request-reply configuration.
* @param logExpression the {@link Expression} to evaluate logger message at runtime
* against the request {@link Message}.
* @return the current {@link IntegrationFlowDefinition}.
@@ -2297,6 +2318,9 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
* the {@code org.springframework.integration.handler.LoggingHandler}
* as a default logging category and SpEL expression to evaluate
* logger message at runtime against the request {@link Message}.
* <p> When this operator is used in the end of flow, it is treated
* as one-way handler without any replies to continue.
* The {@link #logAndReply()} should be used for request-reply configuration.
* @param level the {@link LoggingHandler.Level}.
* @param logExpression the {@link Expression} to evaluate logger message at runtime
* against the request {@link Message}.
@@ -2307,13 +2331,15 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
return log(level, null, logExpression);
}
/**
* Populate a {@link WireTap} for the {@link #currentMessageChannel}
* with the {@link LoggingHandler} subscriber for the {@code INFO}
* {@link LoggingHandler.Level} logging level,
* the provided logging category and SpEL expression to evaluate
* logger message at runtime against the request {@link Message}.
* <p> When this operator is used in the end of flow, it is treated
* as one-way handler without any replies to continue.
* The {@link #logAndReply()} should be used for request-reply configuration.
* @param category the logging category.
* @param logExpression the {@link Expression} to evaluate logger message at runtime
* against the request {@link Message}.
@@ -2330,6 +2356,9 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
* {@link LoggingHandler.Level} logging level,
* the {@code org.springframework.integration.handler.LoggingHandler}
* as a default logging category and {@link Function} for the log message.
* <p> When this operator is used in the end of flow, it is treated
* as one-way handler without any replies to continue.
* The {@link #logAndReply()} should be used for request-reply configuration.
* @param level the {@link LoggingHandler.Level}.
* @param function the function to evaluate logger message at runtime
* @param <P> the expected payload type.
@@ -2346,6 +2375,9 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
* with the {@link LoggingHandler} subscriber for the provided
* {@link LoggingHandler.Level} logging level,
* the provided logging category and {@link Function} for the log message.
* <p> When this operator is used in the end of flow, it is treated
* as one-way handler without any replies to continue.
* The {@link #logAndReply()} should be used for request-reply configuration.
* @param category the logging category.
* @param function the function to evaluate logger message at runtime
* @param <P> the expected payload type.
@@ -2362,6 +2394,9 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
* with the {@link LoggingHandler} subscriber for the provided
* {@link LoggingHandler.Level} logging level, logging category
* and {@link Function} for the log message.
* <p> When this operator is used in the end of flow, it is treated
* as one-way handler without any replies to continue.
* The {@link #logAndReply()} should be used for request-reply configuration.
* @param level the {@link LoggingHandler.Level}.
* @param category the logging category.
* @param function the function to evaluate logger message at runtime
@@ -2375,12 +2410,14 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
return log(level, category, new FunctionExpression<>(function));
}
/**
* Populate a {@link WireTap} for the {@link #currentMessageChannel}
* with the {@link LoggingHandler} subscriber for the provided
* {@link LoggingHandler.Level} logging level, logging category
* and SpEL expression for the log message.
* <p> When this operator is used in the end of flow, it is treated
* as one-way handler without any replies to continue.
* The {@link #logAndReply()} should be used for request-reply configuration.
* @param level the {@link LoggingHandler.Level}.
* @param category the logging category.
* @param logExpression the {@link Expression} to evaluate logger message at runtime
@@ -2406,6 +2443,264 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
return wireTap(loggerChannel);
}
/**
* Populate a {@link WireTap} for the {@link #currentMessageChannel}
* with the {@link LoggingHandler} subscriber for the {@code INFO}
* logging level and {@code org.springframework.integration.handler.LoggingHandler}
* as a default logging category.
* <p> The full request {@link Message} will be logged.
* <p> A {@link #bridge()} is added after this operator to make the flow reply-producing
* if the {@code replyChannel} header is present.
* <p> This operator can be used only in the end of flow.
* @return an {@link IntegrationFlow} instance based on this builder.
* @see #log()
* @see #bridge()
*/
public IntegrationFlow logAndReply() {
return logAndReply(LoggingHandler.Level.INFO);
}
/**
* Populate a {@link WireTap} for the {@link #currentMessageChannel}
* with the {@link LoggingHandler} subscriber for provided {@link LoggingHandler.Level}
* logging level and {@code org.springframework.integration.handler.LoggingHandler}
* as a default logging category.
* <p> The full request {@link Message} will be logged.
* <p> A {@link #bridge()} is added after this operator to make the flow reply-producing
* if the {@code replyChannel} header is present.
* <p> This operator can be used only in the end of flow.
* @param level the {@link LoggingHandler.Level}.
* @return an {@link IntegrationFlow} instance based on this builder.
* @see #log()
* @see #bridge()
*/
public IntegrationFlow logAndReply(LoggingHandler.Level level) {
return logAndReply(level, (String) null);
}
/**
* Populate a {@link WireTap} for the {@link #currentMessageChannel}
* with the {@link LoggingHandler} subscriber for the provided logging category
* and {@code INFO} logging level.
* <p> The full request {@link Message} will be logged.
* <p> A {@link #bridge()} is added after this operator to make the flow reply-producing
* if the {@code replyChannel} header is present.
* <p> This operator can be used only in the end of flow.
* @param category the logging category to use.
* @return an {@link IntegrationFlow} instance based on this builder.
* @see #log()
* @see #bridge()
*/
public IntegrationFlow logAndReply(String category) {
return logAndReply(LoggingHandler.Level.INFO, category);
}
/**
* Populate a {@link WireTap} for the {@link #currentMessageChannel}
* with the {@link LoggingHandler} subscriber for the provided
* {@link LoggingHandler.Level} logging level and logging category.
* <p> The full request {@link Message} will be logged.
* <p> A {@link #bridge()} is added after this operator to make the flow reply-producing
* if the {@code replyChannel} header is present.
* <p> This operator can be used only in the end of flow.
* @param level the {@link LoggingHandler.Level}.
* @param category the logging category to use.
* @return an {@link IntegrationFlow} instance based on this builder.
* @see #log()
* @see #bridge()
*/
public IntegrationFlow logAndReply(LoggingHandler.Level level, String category) {
return logAndReply(level, category, (Expression) null);
}
/**
* Populate a {@link WireTap} for the {@link #currentMessageChannel}
* with the {@link LoggingHandler} subscriber for the provided
* {@link LoggingHandler.Level} logging level, logging category
* and SpEL expression for the log message.
* <p> A {@link #bridge()} is added after this operator to make the flow reply-producing
* if the {@code replyChannel} header is present.
* <p> This operator can be used only in the end of flow.
* @param level the {@link LoggingHandler.Level}.
* @param category the logging category.
* @param logExpression the SpEL expression to evaluate logger message at runtime
* against the request {@link Message}.
* @return an {@link IntegrationFlow} instance based on this builder.
* @see #log()
* @see #bridge()
*/
public IntegrationFlow logAndReply(LoggingHandler.Level level, String category, String logExpression) {
Assert.hasText(logExpression, "'logExpression' must not be empty");
return logAndReply(level, category, PARSER.parseExpression(logExpression));
}
/**
* Populate a {@link WireTap} for the {@link #currentMessageChannel}
* with the {@link LoggingHandler} subscriber for the {@code INFO} logging level,
* the {@code org.springframework.integration.handler.LoggingHandler}
* as a default logging category and {@link Function} for the log message.
* <p> A {@link #bridge()} is added after this operator to make the flow reply-producing
* if the {@code replyChannel} header is present.
* <p> This operator can be used only in the end of flow.
* @param function the function to evaluate logger message at runtime
* @param <P> the expected payload type.
* against the request {@link Message}.
* @return an {@link IntegrationFlow} instance based on this builder.
* @see #log()
* @see #bridge()
*/
public <P> IntegrationFlow logAndReply(Function<Message<P>, Object> function) {
Assert.notNull(function, "'function' must not be null");
return logAndReply(new FunctionExpression<>(function));
}
/**
* Populate a {@link WireTap} for the {@link #currentMessageChannel}
* with the {@link LoggingHandler} subscriber for the {@code INFO} logging level,
* the {@code org.springframework.integration.handler.LoggingHandler}
* as a default logging category and SpEL expression to evaluate
* logger message at runtime against the request {@link Message}.
* <p> A {@link #bridge()} is added after this operator to make the flow reply-producing
* if the {@code replyChannel} header is present.
* <p> This operator can be used only in the end of flow.
* @param logExpression the {@link Expression} to evaluate logger message at runtime
* against the request {@link Message}.
* @return an {@link IntegrationFlow} instance based on this builder.
* @see #log()
* @see #bridge()
*/
public IntegrationFlow logAndReply(Expression logExpression) {
return logAndReply(LoggingHandler.Level.INFO, logExpression);
}
/**
* Populate a {@link WireTap} for the {@link #currentMessageChannel}
* with the {@link LoggingHandler} subscriber for the provided
* {@link LoggingHandler.Level} logging level,
* the {@code org.springframework.integration.handler.LoggingHandler}
* as a default logging category and SpEL expression to evaluate
* logger message at runtime against the request {@link Message}.
* <p> A {@link #bridge()} is added after this operator to make the flow reply-producing
* if the {@code replyChannel} header is present.
* <p> This operator can be used only in the end of flow.
* @param level the {@link LoggingHandler.Level}.
* @param logExpression the {@link Expression} to evaluate logger message at runtime
* against the request {@link Message}.
* @return an {@link IntegrationFlow} instance based on this builder.
* @see #log()
* @see #bridge()
*/
public IntegrationFlow logAndReply(LoggingHandler.Level level, Expression logExpression) {
return logAndReply(level, null, logExpression);
}
/**
* Populate a {@link WireTap} for the {@link #currentMessageChannel}
* with the {@link LoggingHandler} subscriber for the {@code INFO}
* {@link LoggingHandler.Level} logging level,
* the provided logging category and SpEL expression to evaluate
* logger message at runtime against the request {@link Message}.
* <p> A {@link #bridge()} is added after this operator to make the flow reply-producing
* if the {@code replyChannel} header is present.
* <p> This operator can be used only in the end of flow.
* @param category the logging category.
* @param logExpression the {@link Expression} to evaluate logger message at runtime
* against the request {@link Message}.
* @return an {@link IntegrationFlow} instance based on this builder.
* @see #log()
* @see #bridge()
*/
public IntegrationFlow logAndReply(String category, Expression logExpression) {
return logAndReply(LoggingHandler.Level.INFO, category, logExpression);
}
/**
* Populate a {@link WireTap} for the {@link #currentMessageChannel}
* with the {@link LoggingHandler} subscriber for the provided
* {@link LoggingHandler.Level} logging level,
* the {@code org.springframework.integration.handler.LoggingHandler}
* as a default logging category and {@link Function} for the log message.
* <p> A {@link #bridge()} is added after this operator to make the flow reply-producing
* if the {@code replyChannel} header is present.
* <p> This operator can be used only in the end of flow.
* @param level the {@link LoggingHandler.Level}.
* @param function the function to evaluate logger message at runtime
* @param <P> the expected payload type.
* against the request {@link Message}.
* @return an {@link IntegrationFlow} instance based on this builder.
* @see #log()
* @see #bridge()
*/
public <P> IntegrationFlow logAndReply(LoggingHandler.Level level, Function<Message<P>, Object> function) {
return logAndReply(level, null, function);
}
/**
* Populate a {@link WireTap} for the {@link #currentMessageChannel}
* with the {@link LoggingHandler} subscriber for the provided
* {@link LoggingHandler.Level} logging level,
* the provided logging category and {@link Function} for the log message.
* <p> A {@link #bridge()} is added after this operator to make the flow reply-producing
* if the {@code replyChannel} header is present.
* <p> This operator can be used only in the end of flow.
* @param category the logging category.
* @param function the function to evaluate logger message at runtime
* @param <P> the expected payload type.
* against the request {@link Message}.
* @return an {@link IntegrationFlow} instance based on this builder.
* @see #log()
* @see #bridge()
*/
public <P> IntegrationFlow logAndReply(String category, Function<Message<P>, Object> function) {
return logAndReply(LoggingHandler.Level.INFO, category, function);
}
/**
* Populate a {@link WireTap} for the {@link #currentMessageChannel}
* with the {@link LoggingHandler} subscriber for the provided
* {@link LoggingHandler.Level} logging level, logging category
* and {@link Function} for the log message.
* <p> A {@link #bridge()} is added after this operator to make the flow reply-producing
* if the {@code replyChannel} header is present.
* <p> This operator can be used only in the end of flow.
* @param level the {@link LoggingHandler.Level}.
* @param category the logging category.
* @param function the function to evaluate logger message at runtime
* @param <P> the expected payload type.
* against the request {@link Message}.
* @return an {@link IntegrationFlow} instance based on this builder.
* @see #log()
* @see #bridge()
*/
public <P> IntegrationFlow logAndReply(LoggingHandler.Level level, String category,
Function<Message<P>, Object> function) {
Assert.notNull(function, "'function' must not be null");
return logAndReply(level, category, new FunctionExpression<>(function));
}
/**
* Populate a {@link WireTap} for the {@link #currentMessageChannel}
* with the {@link LoggingHandler} subscriber for the provided
* {@link LoggingHandler.Level} logging level, logging category
* and SpEL expression for the log message.
* <p> A {@link #bridge()} is added after this operator to make the flow reply-producing
* if the {@code replyChannel} header is present.
* <p> This operator can be used only in the end of flow.
* @param level the {@link LoggingHandler.Level}.
* @param category the logging category.
* @param logExpression the {@link Expression} to evaluate logger message at runtime
* against the request {@link Message}.
* @return an {@link IntegrationFlow} instance based on this builder.
* @see #log()
* @see #bridge()
*/
public IntegrationFlow logAndReply(LoggingHandler.Level level, String category, Expression logExpression) {
return log(level, category, logExpression)
.bridge()
.get();
}
/**
* Populate a {@link ScatterGatherHandler} to the current integration flow position
* based on the provided {@link MessageChannel} for scattering function
@@ -2603,6 +2898,7 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
@SuppressWarnings("unchecked")
private <S extends ConsumerEndpointSpec<S, ? extends MessageHandler>> B register(S endpointSpec,
Consumer<S> endpointConfigurer) {
if (endpointConfigurer != null) {
endpointConfigurer.accept(endpointSpec);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,11 +29,11 @@ import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
import org.springframework.integration.support.DefaultMessageBuilderFactory;
import org.springframework.integration.transformer.support.HeaderValueMessageProcessor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.MessagingException;
/**
* A Transformer that adds statically configured header values to a Message.
@@ -64,12 +64,13 @@ public class HeaderEnricher extends IntegrationObjectSupport implements Transfor
/**
* Create a HeaderEnricher with the given map of headers.
*
* @param headersToAdd The headers to add.
*/
public HeaderEnricher(Map<String, ? extends HeaderValueMessageProcessor<?>> headersToAdd) {
this.headersToAdd = (headersToAdd != null) ? headersToAdd
: new HashMap<String, HeaderValueMessageProcessor<Object>>();
this.headersToAdd =
headersToAdd != null
? headersToAdd
: new HashMap<String, HeaderValueMessageProcessor<Object>>();
}
public <T> void setMessageProcessor(MessageProcessor<T> messageProcessor) {
@@ -86,7 +87,6 @@ public class HeaderEnricher extends IntegrationObjectSupport implements Transfor
* <code>true</code>. Set this to <code>false</code> if a
* <code>null</code> value should trigger <i>removal</i> of the
* corresponding header instead.
*
* @param shouldSkipNulls true when null values should be skipped.
*/
public void setShouldSkipNulls(boolean shouldSkipNulls) {
@@ -96,52 +96,56 @@ public class HeaderEnricher extends IntegrationObjectSupport implements Transfor
@Override
public String getComponentType() {
return "transformer"; // backwards compatibility
return "header-enricher";
}
@Override
public Message<?> transform(Message<?> message) {
try {
Map<String, Object> headerMap = new HashMap<String, Object>(message.getHeaders());
this.addHeadersFromMessageProcessor(message, headerMap);
for (Map.Entry<String, ? extends HeaderValueMessageProcessor<?>> entry : this.headersToAdd.entrySet()) {
String key = entry.getKey();
HeaderValueMessageProcessor<?> valueProcessor = entry.getValue();
MessageHeaders messageHeaders = message.getHeaders();
Boolean shouldOverwrite = valueProcessor.isOverwrite();
if (shouldOverwrite == null) {
shouldOverwrite = this.defaultOverwrite;
}
AbstractIntegrationMessageBuilder<?> messageBuilder =
getMessageBuilderFactory()
.fromMessage(message);
boolean headerDoesNotExist = headerMap.get(key) == null;
addHeadersFromMessageProcessor(message, messageBuilder);
for (Map.Entry<String, ? extends HeaderValueMessageProcessor<?>> entry : this.headersToAdd.entrySet()) {
String key = entry.getKey();
HeaderValueMessageProcessor<?> valueProcessor = entry.getValue();
/*
* Only evaluate value expression if necessary
*/
if (headerDoesNotExist || shouldOverwrite) {
Object value = valueProcessor.processMessage(message);
if (value != null || !this.shouldSkipNulls) {
headerMap.put(key, value);
}
Boolean shouldOverwrite = valueProcessor.isOverwrite();
if (shouldOverwrite == null) {
shouldOverwrite = this.defaultOverwrite;
}
boolean headerDoesNotExist = messageHeaders.get(key) == null;
/*
* Only evaluate value expression if necessary
*/
if (headerDoesNotExist || shouldOverwrite) {
Object value = valueProcessor.processMessage(message);
if (value != null || !this.shouldSkipNulls) {
messageBuilder.setHeader(key, value);
}
}
return this.getMessageBuilderFactory().withPayload(message.getPayload()).copyHeaders(headerMap).build();
}
catch (Exception e) {
throw new MessagingException(message, "failed to transform message headers", e);
}
return messageBuilder.build();
}
private void addHeadersFromMessageProcessor(Message<?> message, Map<String, Object> headerMap) {
private void addHeadersFromMessageProcessor(Message<?> message,
AbstractIntegrationMessageBuilder<?> messageBuilder) {
if (this.messageProcessor != null) {
Object result = this.messageProcessor.processMessage(message);
if (result instanceof Map) {
MessageHeaders messageHeaders = message.getHeaders();
Map<?, ?> resultMap = (Map<?, ?>) result;
for (Entry<?, ?> entry : resultMap.entrySet()) {
Object key = entry.getKey();
if (key instanceof String) {
if (this.defaultOverwrite || headerMap.get(key) == null) {
headerMap.put((String) key, entry.getValue());
if (this.defaultOverwrite || messageHeaders.get(key) == null) {
messageBuilder.setHeader((String) key, entry.getValue());
}
}
else if (logger.isDebugEnabled()) {

View File

@@ -133,8 +133,7 @@ public class FlowServiceTests {
@Bean
public IntegrationFlow testGateway() {
return f -> f.gateway("processChannel", g -> g.replyChannel("replyChannel"))
.log()
.bridge();
.logAndReply();
}
@Bean

View File

@@ -266,7 +266,7 @@ public class TransformerTests {
.propertyFunction("date", m -> new Date())
.headerExpression("foo", "payload['name']")
)
.get();
.logAndReply();
}
@Bean

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -61,7 +61,8 @@ public class MessageHistoryIntegrationTests {
for (ConsumerEndpointFactoryBean cefBean : cefBeans.values()) {
DirectFieldAccessor bridgeAccessor = new DirectFieldAccessor(cefBean);
String handlerClassName = bridgeAccessor.getPropertyValue("handler").getClass().getName();
assertFalse("org.springframework.integration.config.MessageHistoryWritingMessageHandler".equals(handlerClassName));
assertFalse("org.springframework.integration.config.MessageHistoryWritingMessageHandler"
.equals(handlerClassName));
}
ac.close();
}
@@ -72,10 +73,12 @@ public class MessageHistoryIntegrationTests {
MessageHistoryIntegrationTests.class);
SampleGateway gateway = ac.getBean("sampleGateway", SampleGateway.class);
DirectChannel endOfThePipeChannel = ac.getBean("endOfThePipeChannel", DirectChannel.class);
MessageHandler handler = Mockito.spy(new MessageHandler() {
MessageHandler handler = Mockito.spy(new MessageHandler() { // Not a lambda: Mockito can't mock final classes
@Override
public void handleMessage(Message<?> message) {
Iterator<Properties> historyIterator = message.getHeaders().get(MessageHistory.HEADER_NAME, MessageHistory.class).iterator();
Iterator<Properties> historyIterator = message.getHeaders()
.get(MessageHistory.HEADER_NAME, MessageHistory.class).iterator();
Properties event = historyIterator.next();
assertEquals("sampleGateway", event.getProperty(MessageHistory.NAME_PROPERTY));
@@ -95,7 +98,7 @@ public class MessageHistoryIntegrationTests {
event = historyIterator.next();
assertEquals("testHeaderEnricher", event.getProperty(MessageHistory.NAME_PROPERTY));
assertEquals("transformer", event.getProperty(MessageHistory.TYPE_PROPERTY));
assertEquals("header-enricher", event.getProperty(MessageHistory.TYPE_PROPERTY));
event = historyIterator.next();
assertEquals("chainChannel", event.getProperty(MessageHistory.NAME_PROPERTY));
@@ -106,7 +109,8 @@ public class MessageHistoryIntegrationTests {
assertEquals("chain", event.getProperty(MessageHistory.TYPE_PROPERTY));
event = historyIterator.next();
assertEquals("sampleChain$child.service-activator-within-chain", event.getProperty(MessageHistory.NAME_PROPERTY));
assertEquals("sampleChain$child.service-activator-within-chain", event
.getProperty(MessageHistory.NAME_PROPERTY));
assertEquals("service-activator", event.getProperty(MessageHistory.TYPE_PROPERTY));
event = historyIterator.next();
@@ -155,7 +159,8 @@ public class MessageHistoryIntegrationTests {
MessageHistoryIntegrationTests.class);
SampleGateway gateway = ac.getBean("sampleGateway", SampleGateway.class);
DirectChannel endOfThePipeChannel = ac.getBean("endOfThePipeChannel", DirectChannel.class);
MessageHandler handler = Mockito.spy(new MessageHandler() {
MessageHandler handler = Mockito.spy(new MessageHandler() { // Not a lambda: Mockito can't mock final classes
@Override
public void handleMessage(Message<?> message) {
assertNull(message.getHeaders().get(MessageHistory.HEADER_NAME, MessageHistory.class));
@@ -175,7 +180,8 @@ public class MessageHistoryIntegrationTests {
"messageHistoryWithHistoryWriterNamespace.xml", MessageHistoryIntegrationTests.class);
SampleGateway gateway = ac.getBean("sampleGateway", SampleGateway.class);
DirectChannel endOfThePipeChannel = ac.getBean("endOfThePipeChannel", DirectChannel.class);
MessageHandler handler = Mockito.spy(new MessageHandler() {
MessageHandler handler = Mockito.spy(new MessageHandler() { // Not a lambda: Mockito can't mock final classes
@Override
public void handleMessage(Message<?> message) {
Iterator<Properties> historyIterator = message.getHeaders()
@@ -197,10 +203,12 @@ public class MessageHistoryIntegrationTests {
"messageHistoryWithHistoryWriterNamespaceAndPatterns.xml", MessageHistoryIntegrationTests.class);
SampleGateway gateway = ac.getBean("sampleGateway", SampleGateway.class);
DirectChannel endOfThePipeChannel = ac.getBean("endOfThePipeChannel", DirectChannel.class);
MessageHandler handler = Mockito.spy(new MessageHandler() {
MessageHandler handler = Mockito.spy(new MessageHandler() { // Not a lambda: Mockito can't mock final classes
@Override
public void handleMessage(Message<?> message) {
Iterator<Properties> historyIterator = message.getHeaders().get(MessageHistory.HEADER_NAME, MessageHistory.class).iterator();
Iterator<Properties> historyIterator = message.getHeaders()
.get(MessageHistory.HEADER_NAME, MessageHistory.class).iterator();
assertTrue(historyIterator.hasNext());
Properties gatewayHistory = historyIterator.next();
assertEquals("sampleGateway", gatewayHistory.get("name"));
@@ -224,7 +232,8 @@ public class MessageHistoryIntegrationTests {
MessageHistoryIntegrationTests.class).close();
}
@Test @Ignore
@Test
@Ignore
public void testMessageHistoryWithHistoryPerformance() {
ConfigurableApplicationContext acWithHistory = new ClassPathXmlApplicationContext("perfWithMessageHistory.xml",
MessageHistoryIntegrationTests.class);
@@ -264,7 +273,9 @@ public class MessageHistoryIntegrationTests {
}
public interface SampleGateway {
Message<?> echo(String value);
}
}

View File

@@ -511,14 +511,31 @@ public IntegrationFlow integerFlow() {
----
====
We also register a `BytesToIntegerConverter` within `ConversionService` to get rid of that additional `.transform()`.
// TODO We don't show how to register a `BytesToIntegerConverter` within `ConversionService` to get rid of that additional `.transform()`.
We also can register some `BytesToIntegerConverter` within `ConversionService` to get rid of that additional `.transform()`:
====
[source,java]
----
@Bean
@IntegrationConverter
public BytesToIntegerConverter bytesToIntegerConverter() {
return new BytesToIntegerConverter();
}
@Bean
public IntegrationFlow integerFlow() {
return IntegrationFlows.from("input")
.handle(Integer.class, (p, h) -> p * 2)
.get();
}
----
====
[[java-dsl-log]]
=== Operator log()
For convenience, to log the message journey through the Spring Integration flow (`<logging-channel-adapter>`), a `log()` operator is presented.
Internally, it is represented by the `WireTap` `ChannelInterceptor` and `LoggingHandler` as subscriber.
Internally, it is represented by the `WireTap` `ChannelInterceptor` with a `LoggingHandler` as its subscriber.
It is responsible for logging the incoming message into the next endpoint or the current channel.
The following example shows how to use `LoggingHandler`:
@@ -533,6 +550,10 @@ The following example shows how to use `LoggingHandler`:
In the preceding example, an `id` header is logged at the `ERROR` level onto `test.category` only for messages that passed the filter and before routing.
When this operator is used at the end of a flow, it is a one-way handler and the flow ends.
To make it as a reply-producing flow, you can either use a simple `bridge()` after the `log()` or, starting with version 5.1, you can use a `logAndReply()` operator instead.
`logAndReply` can only be used at the end of a flow.
[[java-dsl-wiretap]]
=== `MessageChannelSpec.wireTap()`
@@ -589,26 +610,6 @@ The following example does not have any channel declaration:
In the preceding example (and any time no channel has been declared), an implicit `DirectChannel` is injected in the current position of the `IntegrationFlow` and used as an output channel for the currently configured `ServiceActivatingHandler` (from the `.handle()`, <<java-dsl-handle,described earlier>>).
[IMPORTANT]
====
If `log()` or `wireTap()` are used in the end of the flow, they are considered to be one-way `MessageHandler` instances.
If you expect the integration flow to return a reply, you should add a `bridge()` should to the end, after `log()` or `wireTap()`, as the following example shows:
[source,java]
----
@Bean
public IntegrationFlow sseFlow() {
return IntegrationFlows
.from(WebFlux.inboundGateway("/sse")
.requestMapping(m ->
m.produces(MediaType.TEXT_EVENT_STREAM_VALUE)))
.handle((p, h) -> Flux.just("foo", "bar", "baz"))
.log(LoggingHandler.Level.WARN)
.bridge()
.get();
}
----
====
[[java-dsl-flows]]
=== Working With Message Flows

View File

@@ -36,6 +36,9 @@ The following changes have been made in version 5.1:
The `IntegrationFlowContext` is now an interface and `IntegrationFlowRegistration` is an inner interface of `IntegrationFlowContext`.
A new `logAndReply()` operator has been introduced for convenience when you wish to log at the end of a flow for request-reply configurations.
This avoid confusion with `log()` which is treated as a one-way end flow component.
[[x5.1-dispatcher-exceptions]]
==== Dispatcher Exceptions