INT-4284: Exception to overwrite id or timestamp

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

To inform end-user that he/she can't override `id` and `timestamp` headers
throw a `BeanInitializationException` from the `gateway`, `header-enricher`,
`enricher` and `header-filter`  configuration when `id` and `timestamp` are
explicitly provided
This commit is contained in:
Artem Bilan
2017-06-06 13:53:49 -04:00
committed by Gary Russell
parent 99224f54c0
commit 4a47a7ce45
11 changed files with 353 additions and 49 deletions

View File

@@ -242,7 +242,7 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
"payload or Message. Found more than one on method [" + methodParameter.getMethod() + "]"); "payload or Message. Found more than one on method [" + methodParameter.getMethod() + "]");
} }
private String determineHeaderName(Annotation headerAnnotation, MethodParameter methodParameter) { static String determineHeaderName(Annotation headerAnnotation, MethodParameter methodParameter) {
String valueAttribute = (String) AnnotationUtils.getValue(headerAnnotation); String valueAttribute = (String) AnnotationUtils.getValue(headerAnnotation);
String headerName = StringUtils.hasText(valueAttribute) ? valueAttribute : methodParameter.getParameterName(); String headerName = StringUtils.hasText(valueAttribute) ? valueAttribute : methodParameter.getParameterName();
Assert.notNull(headerName, "Cannot determine header name. Possible reasons: -debug is " + Assert.notNull(headerName, "Cannot determine header name. Possible reasons: -debug is " +
@@ -250,11 +250,10 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
return headerName; return headerName;
} }
private static List<MethodParameter> getMethodParameterList(Method method) { static List<MethodParameter> getMethodParameterList(Method method) {
List<MethodParameter> parameterList = new LinkedList<>(); List<MethodParameter> parameterList = new LinkedList<>();
ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
int parameterCount = method.getParameterTypes().length; for (int i = 0; i < method.getParameterCount(); i++) {
for (int i = 0; i < parameterCount; i++) {
MethodParameter methodParameter = new SynthesizingMethodParameter(method, i); MethodParameter methodParameter = new SynthesizingMethodParameter(method, i);
methodParameter.initParameterNameDiscovery(parameterNameDiscoverer); methodParameter.initParameterNameDiscovery(parameterNameDiscoverer);
parameterList.add(methodParameter); parameterList.add(methodParameter);
@@ -307,8 +306,7 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
foundPayloadAnnotation = true; foundPayloadAnnotation = true;
} }
else if (annotation.annotationType().equals(Header.class)) { else if (annotation.annotationType().equals(Header.class)) {
String headerName = String headerName = determineHeaderName(annotation, methodParameter);
GatewayMethodInboundMessageMapper.this.determineHeaderName(annotation, methodParameter);
if ((Boolean) AnnotationUtils.getValue(annotation, "required") && argumentValue == null) { if ((Boolean) AnnotationUtils.getValue(annotation, "required") && argumentValue == null) {
throw new IllegalArgumentException("Received null argument value for required header: '" throw new IllegalArgumentException("Received null argument value for required header: '"
+ headerName + "'"); + headerName + "'");

View File

@@ -22,7 +22,10 @@ import java.lang.reflect.Type;
import java.lang.reflect.UndeclaredThrowableException; import java.lang.reflect.UndeclaredThrowableException;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -39,7 +42,9 @@ import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.task.AsyncListenableTaskExecutor; import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.AsyncTaskExecutor;
@@ -54,6 +59,7 @@ import org.springframework.integration.annotation.GatewayHeader;
import org.springframework.integration.endpoint.AbstractEndpoint; import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.expression.ExpressionUtils; import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.expression.ValueExpression; import org.springframework.integration.expression.ValueExpression;
import org.springframework.integration.support.DefaultMessageBuilderFactory;
import org.springframework.integration.support.channel.BeanFactoryChannelResolver; import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
import org.springframework.integration.support.management.TrackableComponent; import org.springframework.integration.support.management.TrackableComponent;
import org.springframework.integration.support.utils.IntegrationUtils; import org.springframework.integration.support.utils.IntegrationUtils;
@@ -62,6 +68,7 @@ import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.MessagingException; import org.springframework.messaging.MessagingException;
import org.springframework.messaging.core.DestinationResolver; import org.springframework.messaging.core.DestinationResolver;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@@ -622,6 +629,33 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
headers = new HashMap<>(); headers = new HashMap<>();
headers.put(MessageHeaders.ERROR_CHANNEL, errorChannel); headers.put(MessageHeaders.ERROR_CHANNEL, errorChannel);
} }
if (getMessageBuilderFactory() instanceof DefaultMessageBuilderFactory) {
Set<String> headerNames = new HashSet<>(headerExpressions.keySet());
if (this.globalMethodMetadata != null) {
headerNames.addAll(this.globalMethodMetadata.getHeaderExpressions().keySet());
}
List<MethodParameter> methodParameters = GatewayMethodInboundMessageMapper.getMethodParameterList(method);
for (MethodParameter methodParameter : methodParameters) {
Header header = methodParameter.getParameterAnnotation(Header.class);
if (header != null) {
String headerName = GatewayMethodInboundMessageMapper.determineHeaderName(header, methodParameter);
headerNames.add(headerName);
}
}
for (String header : headerNames) {
if ((MessageHeaders.ID.equals(header) || MessageHeaders.TIMESTAMP.equals(header))) {
throw new BeanInitializationException(
"Messaging Gateway cannot override 'id' and 'timestamp' read-only headers.\n" +
"Wrong headers configuration for " + getComponentName());
}
}
}
GatewayMethodInboundMessageMapper messageMapper = new GatewayMethodInboundMessageMapper(method, GatewayMethodInboundMessageMapper messageMapper = new GatewayMethodInboundMessageMapper(method,
headerExpressions, headerExpressions,
this.globalMethodMetadata != null ? this.globalMethodMetadata.getHeaderExpressions() : null, this.globalMethodMetadata != null ? this.globalMethodMetadata.getHeaderExpressions() : null,

View File

@@ -152,7 +152,12 @@ public final class MessageBuilder<T> extends AbstractIntegrationMessageBuilder<T
*/ */
@Override @Override
public MessageBuilder<T> removeHeader(String headerName) { public MessageBuilder<T> removeHeader(String headerName) {
this.headerAccessor.removeHeader(headerName); if (!this.headerAccessor.isReadOnly(headerName)) {
this.headerAccessor.removeHeader(headerName);
}
else if (logger.isInfoEnabled()) {
logger.info("The header [" + headerName + "] is ignored for removal because it is is readOnly.");
}
return this; return this;
} }

View File

@@ -21,6 +21,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.Lifecycle; import org.springframework.context.Lifecycle;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression; import org.springframework.expression.Expression;
@@ -30,10 +31,12 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.expression.ExpressionUtils; import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.gateway.MessagingGatewaySupport; import org.springframework.integration.gateway.MessagingGatewaySupport;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.support.DefaultMessageBuilderFactory;
import org.springframework.integration.transformer.support.HeaderValueMessageProcessor; import org.springframework.integration.transformer.support.HeaderValueMessageProcessor;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandlingException; import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@@ -49,22 +52,25 @@ import org.springframework.util.ReflectionUtils;
* @author Artem Bilan * @author Artem Bilan
* @author Liujiong * @author Liujiong
* @author Kris Jacyna * @author Kris Jacyna
*
* @since 2.1 * @since 2.1
*/ */
public class ContentEnricher extends AbstractReplyProducingMessageHandler public class ContentEnricher extends AbstractReplyProducingMessageHandler implements Lifecycle {
implements Lifecycle {
/**
* Customized SpelExpressionParser to allow to specify nested properties when paren is null
*/
private final SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); private final SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
private volatile Map<Expression, Expression> nullResultPropertyExpressions = new HashMap<Expression, Expression>(); private volatile Map<Expression, Expression> nullResultPropertyExpressions = new HashMap<>();
private volatile Map<String, HeaderValueMessageProcessor<?>> nullResultHeaderExpressions = private volatile Map<String, HeaderValueMessageProcessor<?>> nullResultHeaderExpressions =
new HashMap<String, HeaderValueMessageProcessor<?>>(); new HashMap<>();
private volatile Map<Expression, Expression> propertyExpressions = new HashMap<Expression, Expression>(); private volatile Map<Expression, Expression> propertyExpressions = new HashMap<>();
private volatile Map<String, HeaderValueMessageProcessor<?>> headerExpressions = private volatile Map<String, HeaderValueMessageProcessor<?>> headerExpressions =
new HashMap<String, HeaderValueMessageProcessor<?>>(); new HashMap<>();
private EvaluationContext sourceEvaluationContext; private EvaluationContext sourceEvaluationContext;
@@ -93,7 +99,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler
private volatile Long replyTimeout; private volatile Long replyTimeout;
public void setNullResultPropertyExpressions(Map<String, Expression> nullResultPropertyExpressions) { public void setNullResultPropertyExpressions(Map<String, Expression> nullResultPropertyExpressions) {
Map<Expression, Expression> localMap = new HashMap<Expression, Expression>(nullResultPropertyExpressions.size()); Map<Expression, Expression> localMap = new HashMap<>(nullResultPropertyExpressions.size());
for (Map.Entry<String, Expression> entry : nullResultPropertyExpressions.entrySet()) { for (Map.Entry<String, Expression> entry : nullResultPropertyExpressions.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
Expression value = entry.getValue(); Expression value = entry.getValue();
@@ -103,8 +109,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler
} }
public void setNullResultHeaderExpressions(Map<String, HeaderValueMessageProcessor<?>> nullResultHeaderExpressions) { public void setNullResultHeaderExpressions(Map<String, HeaderValueMessageProcessor<?>> nullResultHeaderExpressions) {
this.nullResultHeaderExpressions = new HashMap<String, HeaderValueMessageProcessor<?>>( this.nullResultHeaderExpressions = new HashMap<>(nullResultHeaderExpressions);
nullResultHeaderExpressions);
} }
/** /**
@@ -117,7 +122,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler
Assert.notEmpty(propertyExpressions, "propertyExpressions must not be empty"); Assert.notEmpty(propertyExpressions, "propertyExpressions must not be empty");
Assert.noNullElements(propertyExpressions.keySet().toArray(), "propertyExpressions keys must not be empty"); Assert.noNullElements(propertyExpressions.keySet().toArray(), "propertyExpressions keys must not be empty");
Assert.noNullElements(propertyExpressions.values().toArray(), "propertyExpressions values must not be empty"); Assert.noNullElements(propertyExpressions.values().toArray(), "propertyExpressions values must not be empty");
Map<Expression, Expression> localMap = new HashMap<Expression, Expression>(propertyExpressions.size()); Map<Expression, Expression> localMap = new HashMap<>(propertyExpressions.size());
for (Map.Entry<String, Expression> entry : propertyExpressions.entrySet()) { for (Map.Entry<String, Expression> entry : propertyExpressions.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
Expression value = entry.getValue(); Expression value = entry.getValue();
@@ -137,7 +142,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler
Assert.notEmpty(headerExpressions, "headerExpressions must not be empty"); Assert.notEmpty(headerExpressions, "headerExpressions must not be empty");
Assert.noNullElements(headerExpressions.keySet().toArray(), "headerExpressions keys must not be empty"); Assert.noNullElements(headerExpressions.keySet().toArray(), "headerExpressions keys must not be empty");
Assert.noNullElements(headerExpressions.values().toArray(), "headerExpressions values must not be empty"); Assert.noNullElements(headerExpressions.values().toArray(), "headerExpressions values must not be empty");
this.headerExpressions = new HashMap<String, HeaderValueMessageProcessor<?>>(headerExpressions); this.headerExpressions = new HashMap<>(headerExpressions);
} }
/** /**
@@ -316,19 +321,35 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler
targetContext.setBeanResolver(null); targetContext.setBeanResolver(null);
this.targetEvaluationContext = targetContext; this.targetEvaluationContext = targetContext;
if (this.getBeanFactory() != null) { if (getBeanFactory() != null) {
for (HeaderValueMessageProcessor<?> headerValueMessageProcessor : this.headerExpressions.values()) { boolean checkReadOnlyHeaders = getMessageBuilderFactory() instanceof DefaultMessageBuilderFactory;
if (headerValueMessageProcessor instanceof BeanFactoryAware) {
((BeanFactoryAware) headerValueMessageProcessor).setBeanFactory(getBeanFactory()); for (Map.Entry<String, HeaderValueMessageProcessor<?>> entry : this.headerExpressions.entrySet()) {
if (checkReadOnlyHeaders &&
(MessageHeaders.ID.equals(entry.getKey()) || MessageHeaders.TIMESTAMP.equals(entry.getKey()))) {
throw new BeanInitializationException(
"ContentEnricher cannot override 'id' and 'timestamp' read-only headers.\n" +
"Wrong 'headerExpressions' [" + this.headerExpressions
+ "] configuration for " + getComponentName());
}
if (entry.getValue() instanceof BeanFactoryAware) {
((BeanFactoryAware) entry.getValue()).setBeanFactory(getBeanFactory());
} }
} }
for (HeaderValueMessageProcessor<?> headerValueMessageProcessor : this.nullResultHeaderExpressions.values()) {
if (headerValueMessageProcessor instanceof BeanFactoryAware) { for (Map.Entry<String, HeaderValueMessageProcessor<?>> entry : this.nullResultHeaderExpressions.entrySet()) {
((BeanFactoryAware) headerValueMessageProcessor).setBeanFactory(getBeanFactory()); if (checkReadOnlyHeaders &&
(MessageHeaders.ID.equals(entry.getKey()) || MessageHeaders.TIMESTAMP.equals(entry.getKey()))) {
throw new BeanInitializationException(
"ContentEnricher cannot override 'id' and 'timestamp' read-only headers.\n" +
"Wrong 'nullResultHeaderExpressions' [" + this.nullResultHeaderExpressions
+ "] configuration for " + getComponentName());
}
if (entry.getValue() instanceof BeanFactoryAware) {
((BeanFactoryAware) entry.getValue()).setBeanFactory(getBeanFactory());
} }
} }
} }
} }
@Override @Override

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2015 the original author or authors. * Copyright 2002-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -24,12 +24,15 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.context.IntegrationObjectSupport; import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.handler.MessageProcessor; import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.support.DefaultMessageBuilderFactory;
import org.springframework.integration.transformer.support.HeaderValueMessageProcessor; import org.springframework.integration.transformer.support.HeaderValueMessageProcessor;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.MessagingException; import org.springframework.messaging.MessagingException;
/** /**
@@ -112,7 +115,7 @@ public class HeaderEnricher extends IntegrationObjectSupport implements Transfor
boolean headerDoesNotExist = headerMap.get(key) == null; boolean headerDoesNotExist = headerMap.get(key) == null;
/** /*
* Only evaluate value expression if necessary * Only evaluate value expression if necessary
*/ */
if (headerDoesNotExist || shouldOverwrite) { if (headerDoesNotExist || shouldOverwrite) {
@@ -155,23 +158,36 @@ public class HeaderEnricher extends IntegrationObjectSupport implements Transfor
@Override @Override
public void onInit() throws Exception { public void onInit() throws Exception {
boolean shouldOverwrite = this.defaultOverwrite; boolean shouldOverwrite = this.defaultOverwrite;
for (HeaderValueMessageProcessor<?> processor : this.headersToAdd.values()) { boolean checkReadOnlyHeaders = getMessageBuilderFactory() instanceof DefaultMessageBuilderFactory;
if (processor instanceof BeanFactoryAware && this.getBeanFactory() != null) {
((BeanFactoryAware) processor).setBeanFactory(this.getBeanFactory()); for (Entry<String, ? extends HeaderValueMessageProcessor<?>> entry : this.headersToAdd.entrySet()) {
if (checkReadOnlyHeaders &&
(MessageHeaders.ID.equals(entry.getKey()) || MessageHeaders.TIMESTAMP.equals(entry.getKey()))) {
throw new BeanInitializationException(
"HeaderEnricher cannot override 'id' and 'timestamp' read-only headers.\n" +
"Wrong 'headersToAdd' [" + this.headersToAdd
+ "] configuration for " + getComponentName());
}
HeaderValueMessageProcessor<?> processor = entry.getValue();
if (processor instanceof BeanFactoryAware && getBeanFactory() != null) {
((BeanFactoryAware) processor).setBeanFactory(getBeanFactory());
} }
Boolean processorOverwrite = processor.isOverwrite(); Boolean processorOverwrite = processor.isOverwrite();
if (processorOverwrite != null) { if (processorOverwrite != null) {
shouldOverwrite |= processorOverwrite; shouldOverwrite |= processorOverwrite;
} }
} }
if (this.messageProcessor != null if (this.messageProcessor != null
&& this.messageProcessor instanceof BeanFactoryAware && this.messageProcessor instanceof BeanFactoryAware
&& this.getBeanFactory() != null) { && getBeanFactory() != null) {
((BeanFactoryAware) this.messageProcessor).setBeanFactory(this.getBeanFactory()); ((BeanFactoryAware) this.messageProcessor).setBeanFactory(getBeanFactory());
} }
if (!shouldOverwrite && !this.shouldSkipNulls) {
logger.warn(this.getComponentName() if (!shouldOverwrite && !this.shouldSkipNulls && logger.isWarnEnabled()) {
+ " is configured to not overwrite existing headers. 'shouldSkipNulls = false' will have no effect"); logger.warn(getComponentName() +
" is configured to not overwrite existing headers. 'shouldSkipNulls = false' will have no effect");
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2016 the original author or authors. * Copyright 2002-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,9 +16,14 @@
package org.springframework.integration.transformer; package org.springframework.integration.transformer;
import java.util.Arrays;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.integration.context.IntegrationObjectSupport; import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.support.AbstractIntegrationMessageBuilder; import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
import org.springframework.integration.support.DefaultMessageBuilderFactory;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@@ -27,6 +32,8 @@ import org.springframework.util.Assert;
* @author Mark Fisher * @author Mark Fisher
* @author Oleg Zhurakousky * @author Oleg Zhurakousky
* @author Gary Russell * @author Gary Russell
* @author Artem Bilan
*
* @since 2.0 * @since 2.0
*/ */
public class HeaderFilter extends IntegrationObjectSupport implements Transformer { public class HeaderFilter extends IntegrationObjectSupport implements Transformer {
@@ -50,6 +57,22 @@ public class HeaderFilter extends IntegrationObjectSupport implements Transforme
return "header-filter"; return "header-filter";
} }
@Override
protected void onInit() throws Exception {
super.onInit();
if (getMessageBuilderFactory() instanceof DefaultMessageBuilderFactory) {
for (String header : this.headersToRemove) {
if (!header.contains("*")
&& (MessageHeaders.ID.equals(header) || MessageHeaders.TIMESTAMP.equals(header))) {
throw new BeanInitializationException(
"HeaderFilter cannot remove 'id' and 'timestamp' read-only headers.\n" +
"Wrong 'headersToRemove' [" + Arrays.toString(this.headersToRemove)
+ "] configuration for " + getComponentName());
}
}
}
}
@Override @Override
public Message<?> transform(Message<?> message) { public Message<?> transform(Message<?> message) {
AbstractIntegrationMessageBuilder<?> builder = this.getMessageBuilderFactory().fromMessage(message); AbstractIntegrationMessageBuilder<?> builder = this.getMessageBuilderFactory().fromMessage(message);

View File

@@ -16,10 +16,13 @@
package org.springframework.integration.gateway; package org.springframework.integration.gateway;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
@@ -35,6 +38,7 @@ import org.junit.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
@@ -43,6 +47,8 @@ import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.expression.Expression; import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression; import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.GatewayHeader;
import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.endpoint.EventDrivenConsumer;
@@ -50,7 +56,9 @@ import org.springframework.integration.support.utils.IntegrationUtils;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.PollableChannel; import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.support.GenericMessage; import org.springframework.messaging.support.GenericMessage;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@@ -83,6 +91,7 @@ public class GatewayProxyFactoryBeanTests {
startResponder(requestChannel); startResponder(requestChannel);
GenericConversionService cs = new DefaultConversionService(); GenericConversionService cs = new DefaultConversionService();
Converter<String, byte[]> stringToByteConverter = new Converter<String, byte[]>() { Converter<String, byte[]> stringToByteConverter = new Converter<String, byte[]>() {
@Override @Override
public byte[] convert(String source) { public byte[] convert(String source) {
return source.getBytes(); return source.getBytes();
@@ -312,6 +321,7 @@ public class GatewayProxyFactoryBeanTests {
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean(); GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
DirectChannel channel = new DirectChannel(); DirectChannel channel = new DirectChannel();
EventDrivenConsumer consumer = new EventDrivenConsumer(channel, new MessageHandler() { EventDrivenConsumer consumer = new EventDrivenConsumer(channel, new MessageHandler() {
@Override @Override
public void handleMessage(Message<?> message) { public void handleMessage(Message<?> message) {
Method method = ReflectionUtils.findMethod( Method method = ReflectionUtils.findMethod(
@@ -358,6 +368,57 @@ public class GatewayProxyFactoryBeanTests {
assertThat(bar, equalTo("bar")); assertThat(bar, equalTo("bar"));
} }
@Test
public void testIdHeaderOverrideHeaderExpression() {
GatewayProxyFactoryBean gpfb = new GatewayProxyFactoryBean();
gpfb.setBeanFactory(mock(BeanFactory.class));
GatewayMethodMetadata meta = new GatewayMethodMetadata();
meta.setHeaderExpressions(Collections.singletonMap(MessageHeaders.ID, new LiteralExpression("bar")));
gpfb.setGlobalMethodMetadata(meta);
try {
gpfb.afterPropertiesSet();
fail("BeanInitializationException expected");
}
catch (Exception e) {
assertThat(e, instanceOf(BeanInitializationException.class));
assertThat(e.getMessage(), containsString("Messaging Gateway cannot override 'id' and 'timestamp' read-only headers"));
}
}
@Test
public void testIdHeaderOverrideGatewayHeaderAnnotation() {
GatewayProxyFactoryBean gpfb = new GatewayProxyFactoryBean();
gpfb.setBeanFactory(mock(BeanFactory.class));
gpfb.setServiceInterface(HeadersOverwriteService.class);
try {
gpfb.afterPropertiesSet();
fail("BeanInitializationException expected");
}
catch (Exception e) {
assertThat(e, instanceOf(BeanInitializationException.class));
assertThat(e.getMessage(), containsString("Messaging Gateway cannot override 'id' and 'timestamp' read-only headers"));
}
}
@Test
public void testTimeStampHeaderOverrideParamHeaderAnnotation() {
GatewayProxyFactoryBean gpfb = new GatewayProxyFactoryBean();
gpfb.setBeanFactory(mock(BeanFactory.class));
gpfb.setServiceInterface(HeadersParamService.class);
try {
gpfb.afterPropertiesSet();
fail("BeanInitializationException expected");
}
catch (Exception e) {
assertThat(e, instanceOf(BeanInitializationException.class));
assertThat(e.getMessage(), containsString("Messaging Gateway cannot override 'id' and 'timestamp' read-only headers"));
}
}
// @Test // @Test
// public void testHistory() throws Exception { // public void testHistory() throws Exception {
// GenericApplicationContext context = new GenericApplicationContext(); // GenericApplicationContext context = new GenericApplicationContext();
@@ -413,6 +474,18 @@ public class GatewayProxyFactoryBeanTests {
} }
interface HeadersOverwriteService {
@Gateway(headers = @GatewayHeader(name = MessageHeaders.ID, value = "id"))
Message<?> echo(String s);
}
interface HeadersParamService {
Message<?> echo(String s, @Header(MessageHeaders.TIMESTAMP) String foo);
}
interface TestExceptionThrowingInterface { interface TestExceptionThrowingInterface {
String throwCheckedException(String s) throws TestException; String throwCheckedException(String s) throws TestException;
@@ -421,6 +494,7 @@ public class GatewayProxyFactoryBeanTests {
@SuppressWarnings("serial") @SuppressWarnings("serial")
static class TestException extends Exception { static class TestException extends Exception {
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,20 +16,30 @@
package org.springframework.integration.handler; package org.springframework.integration.handler;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.junit.Test; import org.junit.Test;
import org.springframework.messaging.Message; import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.transformer.HeaderEnricher; import org.springframework.integration.transformer.HeaderEnricher;
import org.springframework.integration.transformer.support.StaticHeaderValueMessageProcessor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.handler.annotation.Payload;
/** /**
* @author Mark Fisher * @author Mark Fisher
* @author Artem Bilan
*
* @since 2.0 * @since 2.0
*/ */
public class MethodInvokingHeaderEnricherTests { public class MethodInvokingHeaderEnricherTests {
@@ -89,6 +99,21 @@ public class MethodInvokingHeaderEnricherTests {
assertEquals("ABC", result.getHeaders().get("bar")); assertEquals("ABC", result.getHeaders().get("bar"));
} }
@Test
public void overwriteId() {
HeaderEnricher enricher =
new HeaderEnricher(Collections.singletonMap(MessageHeaders.ID,
new StaticHeaderValueMessageProcessor<>("foo")));
try {
enricher.afterPropertiesSet();
fail("BeanInitializationException expected");
}
catch (Exception e) {
assertThat(e, instanceOf(BeanInitializationException.class));
assertThat(e.getMessage(), containsString("HeaderEnricher cannot override 'id' and 'timestamp' read-only headers."));
}
}
public static class TestBean { public static class TestBean {

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2016 the original author or authors. * Copyright 2002-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.integration.transformer;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalToIgnoringCase; import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNotSame;
@@ -27,6 +28,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -34,6 +36,7 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.expression.Expression; import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression; import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -47,9 +50,11 @@ import org.springframework.integration.endpoint.PollingConsumer;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.handler.ReplyRequiredException; import org.springframework.integration.handler.ReplyRequiredException;
import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.transformer.support.StaticHeaderValueMessageProcessor;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.MessageDeliveryException; import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessageHandlingException; import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.MessageHeaders;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.PeriodicTrigger; import org.springframework.scheduling.support.PeriodicTrigger;
@@ -87,7 +92,7 @@ public class ContentEnricherTests {
public void replyChannelReplyTimingOut() throws Exception { public void replyChannelReplyTimingOut() throws Exception {
final long requestTimeout = 500L; final long requestTimeout = 500L;
final long replyTimeout = 700L; final long replyTimeout = 100L;
final DirectChannel replyChannel = new DirectChannel(); final DirectChannel replyChannel = new DirectChannel();
final QueueChannel requestChannel = new QueueChannel(1); final QueueChannel requestChannel = new QueueChannel(1);
@@ -521,6 +526,40 @@ public class ContentEnricherTests {
assertEquals("failed target", result.getName()); assertEquals("failed target", result.getName());
} }
@Test
public void testOverwriteTimestamp() {
ContentEnricher contentEnricher = new ContentEnricher();
contentEnricher.setHeaderExpressions(
Collections.singletonMap(MessageHeaders.TIMESTAMP, new StaticHeaderValueMessageProcessor<>("foo")));
contentEnricher.setBeanFactory(mock(BeanFactory.class));
try {
contentEnricher.afterPropertiesSet();
fail("BeanInitializationException expected");
}
catch (Exception e) {
assertThat(e, instanceOf(BeanInitializationException.class));
assertThat(e.getMessage(), containsString("ContentEnricher cannot override 'id' and 'timestamp' read-only headers."));
}
}
@Test
public void testOverwriteIdNullResult() {
ContentEnricher contentEnricher = new ContentEnricher();
contentEnricher.setNullResultHeaderExpressions(
Collections.singletonMap(MessageHeaders.ID, new StaticHeaderValueMessageProcessor<>("foo")));
contentEnricher.setBeanFactory(mock(BeanFactory.class));
try {
contentEnricher.afterPropertiesSet();
fail("BeanInitializationException expected");
}
catch (Exception e) {
assertThat(e, instanceOf(BeanInitializationException.class));
assertThat(e.getMessage(), containsString("ContentEnricher cannot override 'id' and 'timestamp' read-only headers."));
}
}
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static final class Source { private static final class Source {

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,24 +16,35 @@
package org.springframework.integration.transformer; package org.springframework.integration.transformer;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.springframework.integration.test.matcher.HeaderMatcher.hasHeaderKey;
import java.util.Date;
import java.util.UUID; import java.util.UUID;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.integration.IntegrationMessageHeaderAccessor; import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
/** /**
* @author Mark Fisher * @author Mark Fisher
* @author Gary Russell * @author Gary Russell
* @author Artem Bilan
*
* @since 2.0 * @since 2.0
*/ */
public class HeaderFilterTests { public class HeaderFilterTests {
@@ -76,4 +87,60 @@ public class HeaderFilterTests {
assertEquals(correlationId, new IntegrationMessageHeaderAccessor(result).getCorrelationId()); assertEquals(correlationId, new IntegrationMessageHeaderAccessor(result).getCorrelationId());
} }
@Test
public void testIdHeaderRemoval() {
HeaderFilter filter = new HeaderFilter("foo", MessageHeaders.ID);
try {
filter.afterPropertiesSet();
fail("BeanInitializationException expected");
}
catch (Exception e) {
assertThat(e, instanceOf(BeanInitializationException.class));
assertThat(e.getMessage(), containsString("HeaderFilter cannot remove 'id' and 'timestamp' read-only headers."));
}
}
@Test
public void testTimestampHeaderRemoval() {
HeaderFilter filter = new HeaderFilter(MessageHeaders.TIMESTAMP);
try {
filter.afterPropertiesSet();
fail("BeanInitializationException expected");
}
catch (Exception e) {
assertThat(e, instanceOf(BeanInitializationException.class));
assertThat(e.getMessage(), containsString("HeaderFilter cannot remove 'id' and 'timestamp' read-only headers."));
}
}
@Test
public void testIdPatternRemoval() {
HeaderFilter filter = new HeaderFilter("*", MessageHeaders.ID);
filter.setPatternMatch(true);
try {
filter.afterPropertiesSet();
fail("BeanInitializationException expected");
}
catch (Exception e) {
assertThat(e, instanceOf(BeanInitializationException.class));
assertThat(e.getMessage(), containsString("HeaderFilter cannot remove 'id' and 'timestamp' read-only headers."));
}
}
@Test
public void testPatternRemoval() {
HeaderFilter filter = new HeaderFilter("time*");
filter.setPatternMatch(true);
filter.afterPropertiesSet();
Message<String> message = MessageBuilder.withPayload("test")
.setHeader("time", new Date())
.build();
Message<?> result = filter.transform(message);
assertThat(result, hasHeaderKey(MessageHeaders.TIMESTAMP));
assertThat(result, not(hasHeaderKey("time")));
}
} }

View File

@@ -374,9 +374,16 @@ In addition to the default strategy, two additional `IdGenerators` are provided;
===== Read-only Headers ===== Read-only Headers
The `MessageHeaders.ID` and `MessageHeaders.TIMESTAMP` are read-only headers and the cannot be overridden. The `MessageHeaders.ID` and `MessageHeaders.TIMESTAMP` are read-only headers and the cannot be overridden.
With the `spring.integration.readOnly.headers` global property (see <<global-properties>>) you can specify any header names which become read-only.
Since _version 4.3.2_, the `MessageBuilder` provides the `readOnlyHeaders(String... readOnlyHeaders)` API to customize a list of headers which should not be copied from an upstream `Message`.
Just the `MessageHeaders.ID` and `MessageHeaders.TIMESTAMP` are read only by default.
The global `spring.integration.readOnly.headers` property (see <<global-properties>>) is provided to customize `DefaultMessageBuilderFactory` for Framework components.
This can be useful when you would like do not populate some out-of-the-box headers, like `contentType` by the `ObjectToJsonTransformer` (see <<json-transformers>>).
When you try to build a new message using `MessageBuilder`, this kind of headers are ignored and particular `INFO` message is emitted to logs. When you try to build a new message using `MessageBuilder`, this kind of headers are ignored and particular `INFO` message is emitted to logs.
Starting with _version 5.0_, <<gateway,Messaging Gateway>>, <<header-enricher,Header Enricher>>, <<payload-enricher,Content Enricher>> and <<header-filter, Header Filter>> don't allow to configure `MessageHeaders.ID` and `MessageHeaders.TIMESTAMP` header names when `DefaultMessageBuilderFactory` is used and they throw `BeanInitializationException`.
[[message-implementations]] [[message-implementations]]
==== Message Implementations ==== Message Implementations
@@ -468,8 +475,3 @@ assertEquals(2, lessImportantMessage.getHeaders().getPriority());
The `priority` header is only considered when using a `PriorityChannel` (as described in the next chapter). The `priority` header is only considered when using a `PriorityChannel` (as described in the next chapter).
It is defined as _java.lang.Integer_. It is defined as _java.lang.Integer_.
Since _version 4.3.2_, the `MessageBuilder` provides the `readOnlyHeaders(String... readOnlyHeaders)` API to customize a list of headers which should not be copied from an upstream `Message`.
Just the `MessageHeaders.ID` and `MessageHeaders.TIMESTAMP` are read only by default.
The global `spring.integration.readOnly.headers` property (see <<global-properties>>) is provided to customize `DefaultMessageBuilderFactory` for Framework components.
This can be useful when you would like do not populate some out-of-the-box headers, like `contentType` by the `ObjectToJsonTransformer` (see <<json-transformers>>).