Merge remote-tracking branch 'upstream/master' into 4.0.0-WIP

Conflicts:
	spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java
	spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java
	spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessor.java
	spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java
	spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/AbstractTxTimeoutMessageStoreTests.java
	spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java

Resolved.
This commit is contained in:
Gary Russell
2013-10-30 23:13:27 -04:00
93 changed files with 1657 additions and 787 deletions

View File

@@ -82,8 +82,7 @@ public class AmqpOutboundEndpoint extends AbstractReplyProducingMessageHandler
private volatile MessageChannel returnChannel;
@Override
protected void onInit() {
super.onInit();
protected void doInit() {
Assert.state(exchangeNameExpression == null || exchangeName == null,
"Either an exchangeName or an exchangeNameExpression can be provided, but not both");
Assert.state(this.confirmCorrelationExpression == null || !this.expectReply,
@@ -116,7 +115,7 @@ public class AmqpOutboundEndpoint extends AbstractReplyProducingMessageHandler
}
if (this.returnChannel != null) {
Assert.isTrue(amqpTemplate instanceof RabbitTemplate, "RabbitTemplate implementation is required for publisher returns");
( (RabbitTemplate) this.amqpTemplate).setReturnCallback(this);
((RabbitTemplate) this.amqpTemplate).setReturnCallback(this);
}
}
@@ -289,7 +288,7 @@ public class AmqpOutboundEndpoint extends AbstractReplyProducingMessageHandler
public void returnedMessage(org.springframework.amqp.core.Message message, int replyCode, String replyText,
String exchange, String routingKey) {
// safe to cast; we asserted we have a RabbitTemplate in onInit()
// safe to cast; we asserted we have a RabbitTemplate in doInit()
MessageConverter converter = ((RabbitTemplate) this.amqpTemplate).getMessageConverter();
Object returnedObject = converter.fromMessage(message);
MessageBuilder<?> builder = (returnedObject instanceof Message)

View File

@@ -77,10 +77,10 @@ abstract class AbstractStandardMessageHandlerFactoryBean extends AbstractSimpleM
if (this.targetObject != null) {
Assert.state(this.expression == null,
"The 'targetObject' and 'expression' properties are mutually exclusive.");
boolean targetIsDirectReplyProducingHandler = this.extractTypeIfPossible(targetObject,
AbstractReplyProducingMessageHandler.class) != null
&& this.canBeUsedDirect(
(AbstractReplyProducingMessageHandler) targetObject) // give subclasses a say
AbstractReplyProducingMessageHandler actualHandler = this.extractTypeIfPossible(targetObject,
AbstractReplyProducingMessageHandler.class);
boolean targetIsDirectReplyProducingHandler = actualHandler != null
&& this.canBeUsedDirect(actualHandler) // give subclasses a say
&& this.methodIsHandleMessageOrEmpty(this.targetMethodName);
if (this.targetObject instanceof MessageProcessor<?>) {
handler = this.createMessageProcessingHandler((MessageProcessor<?>) this.targetObject);
@@ -89,9 +89,9 @@ abstract class AbstractStandardMessageHandlerFactoryBean extends AbstractSimpleM
if (logger.isDebugEnabled()) {
logger.debug("Wiring handler (" + beanName + ") directly into endpoint");
}
this.checkReuse(actualHandler);
this.postProcessReplyProducer(actualHandler);
handler = (MessageHandler) targetObject;
this.checkReuse((AbstractReplyProducingMessageHandler) handler);
this.postProcessReplyProducer((AbstractReplyProducingMessageHandler) handler);
}
else {
handler = this.createMethodInvokingHandler(this.targetObject, this.targetMethodName);
@@ -120,7 +120,7 @@ abstract class AbstractStandardMessageHandlerFactoryBean extends AbstractSimpleM
}
private void checkReuse(AbstractReplyProducingMessageHandler replyHandler) {
Assert.isTrue(!referencedReplyProducers.contains(targetObject),
Assert.isTrue(!referencedReplyProducers.contains(replyHandler),
"An AbstractReplyProducingMessageHandler may only be referenced once (" +
replyHandler.getComponentName() + ") - use scope=\"prototype\"");
referencedReplyProducers.add(replyHandler);

View File

@@ -28,6 +28,7 @@ import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -43,7 +44,7 @@ import org.springframework.util.xml.DomUtils;
public class GatewayParser extends AbstractSimpleBeanDefinitionParser {
private static String[] referenceAttributes = new String[] {
"default-request-channel", "default-reply-channel", "error-channel", "message-mapper", "async-executor"
"default-request-channel", "default-reply-channel", "error-channel", "message-mapper", "async-executor", "mapper"
};
private static String[] innerAttributes = new String[] {
@@ -93,9 +94,16 @@ public class GatewayParser extends AbstractSimpleBeanDefinitionParser {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, attributeName);
}
boolean hasMapper = StringUtils.hasText(element.getAttribute("mapper"));
boolean hasDefaultPayloadExpression = StringUtils.hasText(element.getAttribute("default-payload-expression"));
Assert.state(hasMapper ? !hasDefaultPayloadExpression : true, "'default-payload-expression' is not allowed when a 'mapper' is provided");
List<Element> invocationHeaders = DomUtils.getChildElementsByTagName(element, "default-header");
if (!CollectionUtils.isEmpty(invocationHeaders)
|| StringUtils.hasText(element.getAttribute("default-payload-expression"))) {
boolean hasDefaultHeaders = !CollectionUtils.isEmpty(invocationHeaders);
Assert.state(hasMapper ? !hasDefaultHeaders : true, "default-header elements are not allowed when a 'mapper' is provided");
if (hasDefaultHeaders || hasDefaultPayloadExpression) {
BeanDefinitionBuilder methodMetadataBuilder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.gateway.GatewayMethodMetadata");
this.setMethodInvocationHeaders(methodMetadataBuilder, invocationHeaders);
@@ -118,8 +126,11 @@ public class GatewayParser extends AbstractSimpleBeanDefinitionParser {
methodMetadataBuilder.addPropertyValue("requestTimeout", methodElement.getAttribute("request-timeout"));
methodMetadataBuilder.addPropertyValue("replyTimeout", methodElement.getAttribute("reply-timeout"));
IntegrationNamespaceUtils.setValueIfAttributeDefined(methodMetadataBuilder, methodElement, "payload-expression");
Assert.state(hasMapper ? !StringUtils.hasText(element.getAttribute("payload-expression")) : true,
"'payload-expression' is not allowed when a 'mapper' is provided");
invocationHeaders = DomUtils.getChildElementsByTagName(methodElement, "header");
if (!CollectionUtils.isEmpty(invocationHeaders)) {
Assert.state(!hasMapper, "header elements are not allowed when a 'mapper' is provided");
this.setMethodInvocationHeaders(methodMetadataBuilder, invocationHeaders);
}
methodMetadataMap.put(methodName, methodMetadataBuilder.getBeanDefinition());

View File

@@ -20,12 +20,14 @@ import java.util.List;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -33,14 +35,13 @@ import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.Conventions;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.config.ExpressionFactoryBean;
import org.springframework.integration.config.SpelFunctionFactoryBean;
import org.springframework.integration.endpoint.AbstractPollingEndpoint;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.MatchAlwaysTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
@@ -462,6 +463,7 @@ public abstract class IntegrationNamespaceUtils {
}
return expressionDef;
}
public static void registerSpelFunctionBean(BeanDefinitionRegistry registry, String functionId, String className,
String methodSignature) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SpelFunctionFactoryBean.class)
@@ -469,6 +471,7 @@ public abstract class IntegrationNamespaceUtils {
.addConstructorArgValue(methodSignature);
registry.registerBeanDefinition(functionId, builder.getBeanDefinition());
}
public static BeanDefinition createExpressionDefIfAttributeDefined(String expressionElementName, Element element) {
Assert.hasText(expressionElementName, "'expressionElementName' must no be empty");

View File

@@ -98,8 +98,7 @@ public class MessageFilter extends AbstractReplyProducingPostProcessingMessageHa
}
@Override
public final void onInit() {
super.onInit();
protected void doInit() {
if (this.selector instanceof AbstractMessageProcessingSelector) {
((AbstractMessageProcessingSelector) this.selector).setConversionService(this.getConversionService());
}

View File

@@ -42,6 +42,7 @@ import org.springframework.integration.annotation.Headers;
import org.springframework.integration.annotation.Payload;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.mapping.InboundMessageMapper;
import org.springframework.integration.mapping.MessageMappingException;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -85,6 +86,8 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
private final List<MethodParameter> parameterList;
private final MethodArgsMessageMapper argsMapper;
private volatile Expression payloadExpression;
private final Map<String, Expression> parameterPayloadExpressions = new HashMap<String, Expression>();
@@ -93,23 +96,28 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
private volatile BeanFactory beanFactory;
public GatewayMethodInboundMessageMapper(Method method) {
this(method, null);
}
public GatewayMethodInboundMessageMapper(Method method, Map<String, Expression> headerExpressions) {
this(method, headerExpressions, null);
this(method, headerExpressions, null, null);
}
public GatewayMethodInboundMessageMapper(Method method, Map<String, Expression> headerExpressions,
Map<String, Expression> globalHeaderExpressions) {
Map<String, Expression> globalHeaderExpressions, MethodArgsMessageMapper mapper) {
Assert.notNull(method, "method must not be null");
this.method = method;
this.headerExpressions = headerExpressions;
this.globalHeaderExpressions = globalHeaderExpressions;
this.parameterList = getMethodParameterList(method);
this.payloadExpression = parsePayloadExpression(method);
if (mapper == null) {
this.argsMapper = new DefaultMethodArgsMessageMapper();
}
else {
this.argsMapper = mapper;
}
}
@@ -135,85 +143,17 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
}
private Message<?> mapArgumentsToMessage(Object[] arguments) {
Object messageOrPayload = null;
boolean foundPayloadAnnotation = false;
Map<String, Object> headers = new HashMap<String, Object>();
EvaluationContext methodInvocationEvaluationContext = createMethodInvocationEvaluationContext(arguments);
if (this.payloadExpression != null) {
messageOrPayload = this.payloadExpression.getValue(methodInvocationEvaluationContext);
try {
return this.argsMapper.toMessage(new MethodArgsHolder(this.method, arguments));
}
for (int i = 0; i < this.parameterList.size(); i++) {
Object argumentValue = arguments[i];
MethodParameter methodParameter = this.parameterList.get(i);
Annotation annotation = this.findMappingAnnotation(methodParameter.getParameterAnnotations());
if (annotation != null) {
if (annotation.annotationType().equals(Payload.class)) {
if (messageOrPayload != null) {
this.throwExceptionForMultipleMessageOrPayloadParameters(methodParameter);
}
String expression = ((Payload) annotation).value();
if (!StringUtils.hasText(expression)) {
messageOrPayload = argumentValue;
}
else {
messageOrPayload = this.evaluatePayloadExpression(expression, argumentValue);
}
foundPayloadAnnotation = true;
}
else if (annotation.annotationType().equals(Header.class)) {
Header headerAnnotation = (Header) annotation;
String headerName = this.determineHeaderName(headerAnnotation, methodParameter);
if (headerAnnotation.required() && argumentValue == null) {
throw new IllegalArgumentException("Received null argument value for required header: '" + headerName + "'");
}
headers.put(headerName, argumentValue);
}
else if (annotation.annotationType().equals(Headers.class)) {
if (argumentValue != null) {
if (!(argumentValue instanceof Map)) {
throw new IllegalArgumentException("@Headers annotation is only valid for Map-typed parameters");
}
for (Object key : ((Map<?, ?>) argumentValue).keySet()) {
Assert.isInstanceOf(String.class, key, "Invalid header name [" + key +
"], name type must be String.");
Object value = ((Map<?, ?>) argumentValue).get(key);
headers.put((String) key, value);
}
}
}
catch (Exception e) {
if (e instanceof MessagingException) {
throw (MessagingException) e;
}
else if (messageOrPayload == null) {
messageOrPayload = argumentValue;
}
else if (Map.class.isAssignableFrom(methodParameter.getParameterType())) {
if (messageOrPayload instanceof Map && !foundPayloadAnnotation) {
if (payloadExpression == null){
throw new MessagingException("Ambiguous method parameters; found more than one " +
"Map-typed parameter and neither one contains a @Payload annotation");
}
}
this.copyHeaders((Map<?, ?>) argumentValue, headers);
}
else if (this.payloadExpression == null) {
this.throwExceptionForMultipleMessageOrPayloadParameters(methodParameter);
else {
throw new MessageMappingException("Failed to map arguments", e);
}
}
Assert.isTrue(messageOrPayload != null, "unable to determine a Message or payload parameter on method [" + method + "]");
MessageBuilder<?> builder = (messageOrPayload instanceof Message)
? MessageBuilder.fromMessage((Message<?>) messageOrPayload)
: MessageBuilder.withPayload(messageOrPayload);
builder.copyHeadersIfAbsent(headers);
// Explicit headers in XML override any @Header annotations...
if (!CollectionUtils.isEmpty(this.headerExpressions)) {
Map<String, Object> evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, this.headerExpressions);
builder.copyHeaders(evaluatedHeaders);
}
// ...whereas global (default) headers do not...
if (!CollectionUtils.isEmpty(this.globalHeaderExpressions)) {
Map<String, Object> evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, this.globalHeaderExpressions);
builder.copyHeadersIfAbsent(evaluatedHeaders);
}
return builder.build();
}
private Map<String, Object> evaluateHeaders(EvaluationContext methodInvocationEvaluationContext, Map<String, Expression> headerExpressions) {
@@ -318,4 +258,94 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
return expression;
}
public class DefaultMethodArgsMessageMapper implements MethodArgsMessageMapper {
@Override
public Message<?> toMessage(MethodArgsHolder holder) throws Exception {
Object messageOrPayload = null;
boolean foundPayloadAnnotation = false;
Object[] arguments = holder.getArgs();
EvaluationContext methodInvocationEvaluationContext = createMethodInvocationEvaluationContext(arguments);
Map<String, Object> headers = new HashMap<String, Object>();
if (GatewayMethodInboundMessageMapper.this.payloadExpression != null) {
messageOrPayload = GatewayMethodInboundMessageMapper.this.payloadExpression.getValue(methodInvocationEvaluationContext);
}
for (int i = 0; i < GatewayMethodInboundMessageMapper.this.parameterList.size(); i++) {
Object argumentValue = arguments[i];
MethodParameter methodParameter = GatewayMethodInboundMessageMapper.this.parameterList.get(i);
Annotation annotation = GatewayMethodInboundMessageMapper.this.findMappingAnnotation(methodParameter.getParameterAnnotations());
if (annotation != null) {
if (annotation.annotationType().equals(Payload.class)) {
if (messageOrPayload != null) {
GatewayMethodInboundMessageMapper.this.throwExceptionForMultipleMessageOrPayloadParameters(methodParameter);
}
String expression = ((Payload) annotation).value();
if (!StringUtils.hasText(expression)) {
messageOrPayload = argumentValue;
}
else {
messageOrPayload = GatewayMethodInboundMessageMapper.this.evaluatePayloadExpression(expression, argumentValue);
}
foundPayloadAnnotation = true;
}
else if (annotation.annotationType().equals(Header.class)) {
Header headerAnnotation = (Header) annotation;
String headerName = GatewayMethodInboundMessageMapper.this.determineHeaderName(headerAnnotation, methodParameter);
if (headerAnnotation.required() && argumentValue == null) {
throw new IllegalArgumentException("Received null argument value for required header: '" + headerName + "'");
}
headers.put(headerName, argumentValue);
}
else if (annotation.annotationType().equals(Headers.class)) {
if (argumentValue != null) {
if (!(argumentValue instanceof Map)) {
throw new IllegalArgumentException("@Headers annotation is only valid for Map-typed parameters");
}
for (Object key : ((Map<?, ?>) argumentValue).keySet()) {
Assert.isInstanceOf(String.class, key, "Invalid header name [" + key +
"], name type must be String.");
Object value = ((Map<?, ?>) argumentValue).get(key);
headers.put((String) key, value);
}
}
}
}
else if (messageOrPayload == null) {
messageOrPayload = argumentValue;
}
else if (Map.class.isAssignableFrom(methodParameter.getParameterType())) {
if (messageOrPayload instanceof Map && !foundPayloadAnnotation) {
if (payloadExpression == null){
throw new MessagingException("Ambiguous method parameters; found more than one " +
"Map-typed parameter and neither one contains a @Payload annotation");
}
}
GatewayMethodInboundMessageMapper.this.copyHeaders((Map<?, ?>) argumentValue, headers);
}
else if (GatewayMethodInboundMessageMapper.this.payloadExpression == null) {
GatewayMethodInboundMessageMapper.this.throwExceptionForMultipleMessageOrPayloadParameters(methodParameter);
}
}
Assert.isTrue(messageOrPayload != null, "unable to determine a Message or payload parameter on method [" + method + "]");
MessageBuilder<?> builder = (messageOrPayload instanceof Message)
? MessageBuilder.fromMessage((Message<?>) messageOrPayload)
: MessageBuilder.withPayload(messageOrPayload);
builder.copyHeadersIfAbsent(headers);
// Explicit headers in XML override any @Header annotations...
if (!CollectionUtils.isEmpty(GatewayMethodInboundMessageMapper.this.headerExpressions)) {
Map<String, Object> evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext,
GatewayMethodInboundMessageMapper.this.headerExpressions);
builder.copyHeaders(evaluatedHeaders);
}
// ...whereas global (default) headers do not...
if (!CollectionUtils.isEmpty(GatewayMethodInboundMessageMapper.this.globalHeaderExpressions)) {
Map<String, Object> evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext,
GatewayMethodInboundMessageMapper.this.globalHeaderExpressions);
builder.copyHeadersIfAbsent(evaluatedHeaders);
}
return builder.build();
}
}
}

View File

@@ -105,6 +105,8 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab
private volatile GatewayMethodMetadata globalMethodMetadata;
private volatile MethodArgsMessageMapper argsMapper;
/**
* Create a Factory whose service interface type can be configured by setter injection.
* If none is set, it will fall back to the default service interface type,
@@ -214,6 +216,15 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab
this.beanClassLoader = beanClassLoader;
}
/**
* Provide a custom {@link MethodArgsMessageMapper} to map from a {@link MethodArgsHolder}
* to a {@link Message}.
* @param mapper the mapper.
*/
public final void setMapper(MethodArgsMessageMapper mapper) {
this.argsMapper = mapper;
}
@Override
protected void onInit() {
synchronized (this.initializationMonitor) {
@@ -395,7 +406,8 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab
}
}
GatewayMethodInboundMessageMapper messageMapper = new GatewayMethodInboundMessageMapper(method, headerExpressions,
this.globalMethodMetadata != null ? this.globalMethodMetadata.getHeaderExpressions() : null);
this.globalMethodMetadata != null ? this.globalMethodMetadata.getHeaderExpressions() : null,
this.argsMapper);
if (StringUtils.hasText(payloadExpression)) {
messageMapper.setPayloadExpression(payloadExpression);
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2013 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
*
* http://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.gateway;
import java.lang.reflect.Method;
/**
* Simple wrapper class containing a {@link Method} and an object
* array containing the arguments for an invocation of that method.
* For example used by a {@link MethodArgsMessageMapper} with this generic
* type to provide custom argument mapping when creating a message
* in a {@code GatewayProxyFactoryBean}.
*
* @author Gary Russell
* @since 3.0
*
*/
public final class MethodArgsHolder {
private final Method method;
private final Object[] args;
public MethodArgsHolder(Method method, Object[] args) {
this.method = method;
this.args = args;
}
public final Method getMethod() {
return method;
}
public final Object[] getArgs() {
return args;
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2013 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
*
* http://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.gateway;
import org.springframework.integration.mapping.InboundMessageMapper;
/**
* Implementations of this interface are {@link InboundMessageMapper}s
* that map a {@link MethodArgsHolder} to a {@link org.springframework.integration.Message}.
*
* @author Gary Russell
* @since 3.0
*
*/
public interface MethodArgsMessageMapper extends InboundMessageMapper<MethodArgsHolder> {
}

View File

@@ -19,6 +19,7 @@ package org.springframework.integration.handler;
import java.util.List;
import org.aopalliance.aop.Advice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.integration.core.MessageProducer;
@@ -114,7 +115,7 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
@Override
protected void onInit() {
protected final void onInit() {
if (this.getBeanFactory() != null) {
this.messagingTemplate.setBeanFactory(getBeanFactory());
}
@@ -125,6 +126,10 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
}
this.advisedRequestHandler = (RequestHandler) proxyFactory.getProxy(this.beanClassLoader);
}
this.doInit();
}
protected void doInit() {
}
/**

View File

@@ -196,8 +196,7 @@ public class DelayHandler extends AbstractReplyProducingMessageHandler implement
}
@Override
protected void onInit() {
super.onInit();
protected void doInit() {
if (this.messageStore == null) {
this.messageStore = new SimpleMessageStore();
}

View File

@@ -55,8 +55,7 @@ public class ServiceActivatingHandler extends AbstractReplyProducingMessageHandl
}
@Override
public final void onInit() {
super.onInit();
protected void doInit() {
if (processor instanceof AbstractMessageProcessor) {
((AbstractMessageProcessor<?>) this.processor).setConversionService(this.getConversionService());
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2013 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.
@@ -28,7 +28,7 @@ import org.springframework.util.Assert;
/**
* Base class for Message Splitter implementations that delegate to a
* {@link MessageProcessor} instance.
*
*
* @author Mark Fisher
* @since 2.0
*/
@@ -43,8 +43,7 @@ abstract class AbstractMessageProcessingSplitter extends AbstractMessageSplitter
}
@Override
public void onInit() {
super.onInit();
protected void doInit() {
ConversionService conversionService = this.getConversionService();
if (conversionService != null && this.messageProcessor instanceof AbstractMessageProcessor) {
((AbstractMessageProcessor<?>) this.messageProcessor).setConversionService(conversionService);

View File

@@ -194,8 +194,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
* the requestChannel is set.
*/
@Override
public void onInit() {
super.onInit();
protected void doInit() {
if (this.replyChannel != null) {
Assert.notNull(this.requestChannel, "If the replyChannel is set, then the requestChannel must not be null");
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2013 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.
@@ -54,8 +54,7 @@ public class MessageTransformingHandler extends AbstractReplyProducingMessageHan
}
@Override
protected void onInit() {
super.onInit();
protected void doInit() {
if (this.getBeanFactory() != null && this.transformer instanceof BeanFactoryAware) {
((BeanFactoryAware) this.transformer).setBeanFactory(this.getBeanFactory());
}

View File

@@ -717,6 +717,22 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mapper" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
An MethodArgsMessageMapper to map the method arguments to a Message. When this
is provided, no payload-expressions or headers are allowed; the custom mapper is
responsible for creating the message.
]]>
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.gateway.MethodArgsMessageMapper" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>

View File

@@ -23,16 +23,20 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.expression.Expression;
import org.springframework.integration.Message;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.handler.DelayHandler;
import org.springframework.integration.test.util.TestUtils;
@@ -48,6 +52,7 @@ import org.springframework.transaction.interceptor.TransactionInterceptor;
* @author Mark Fisher
* @author Artem Bilan
* @author Gunnar Hillert
* @author Gary Russell
* @since 1.0.3
*/
@RunWith(SpringJUnit4ClassRunner.class)
@@ -100,7 +105,7 @@ public class DelayerParserTests {
}
@Test //INT-2649
public void transactionalSubElement() {
public void transactionalSubElement() throws Exception {
Object endpoint = context.getBean("delayerWithTransactional");
DelayHandler delayHandler = TestUtils.getPropertyValue(endpoint, "handler", DelayHandler.class);
List<?> adviceChain = TestUtils.getPropertyValue(delayHandler, "delayedAdviceChain", List.class);
@@ -109,7 +114,8 @@ public class DelayerParserTests {
assertTrue(advice instanceof TransactionInterceptor);
TransactionAttributeSource transactionAttributeSource = ((TransactionInterceptor) advice).getTransactionAttributeSource();
assertTrue(transactionAttributeSource instanceof MatchAlwaysTransactionAttributeSource);
TransactionDefinition definition = transactionAttributeSource.getTransactionAttribute(null, null);
Method method = MessageHandler.class.getMethod("handleMessage", Message.class);
TransactionDefinition definition = transactionAttributeSource.getTransactionAttribute(method, null);
assertEquals(TransactionDefinition.PROPAGATION_REQUIRED, definition.getPropagationBehavior());
assertEquals(TransactionDefinition.ISOLATION_DEFAULT, definition.getIsolationLevel());
assertEquals(TransactionDefinition.TIMEOUT_DEFAULT, definition.getTimeout());

View File

@@ -39,12 +39,12 @@ import org.springframework.util.StopWatch;
/**
* @author Oleg Zhurakousky
* @author Gunnar Hillert
* @author Gary Russell
*/
public class MessageIdGenerationTests {
@Test
public void testCustomIdGenerationWithParentRegistrar() throws Exception{
public void testCustomIdGenerationWithParentRegistrar() throws Exception {
ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("MessageIdGenerationTests-context-withGenerator.xml", this.getClass());
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext(new String[]{"MessageIdGenerationTests-context.xml"}, this.getClass(), parent);
@@ -58,7 +58,7 @@ public class MessageIdGenerationTests {
}
@Test
public void testCustomIdGenerationWithParentChileIndependentCreation() throws Exception{
public void testCustomIdGenerationWithParentChildIndependentCreation() throws Exception {
ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("MessageIdGenerationTests-context-withGenerator.xml", this.getClass());
GenericXmlApplicationContext child = new GenericXmlApplicationContext();
child.load("classpath:/org/springframework/integration/core/MessageIdGenerationTests-context.xml");
@@ -89,7 +89,7 @@ public class MessageIdGenerationTests {
}
@Test
public void testCustomIdGenerationWithChildRegistrar() throws Exception{
public void testCustomIdGenerationWithChildRegistrar() throws Exception {
ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("MessageIdGenerationTests-context.xml", this.getClass());
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext(new String[]{"MessageIdGenerationTests-context-withGenerator.xml"}, this.getClass(), parent);
@@ -98,13 +98,13 @@ public class MessageIdGenerationTests {
MessageChannel inputChannel = child.getBean("input", MessageChannel.class);
inputChannel.send(new GenericMessage<Integer>(0));
verify(idGenerator, atLeastOnce()).generateId();
parent.close();
child.close();
parent.close();
this.assertDestroy();
}
@Test
public void testCustomIdGenerationWithChildRegistrarClosed() throws Exception{
public void testCustomIdGenerationWithChildRegistrarClosed() throws Exception {
ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("MessageIdGenerationTests-context.xml", this.getClass());
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext(new String[]{"MessageIdGenerationTests-context-withGenerator.xml"}, this.getClass(), parent);
@@ -120,7 +120,7 @@ public class MessageIdGenerationTests {
// similar to the last test, but should not fail because child AC is closed before second child AC is started
@Test
public void testCustomIdGenerationWithParentChildIndependentCreationChildrenRegistrarsOneAtTheTime() throws Exception{
public void testCustomIdGenerationWithParentChildIndependentCreationChildrenRegistrarsOneAtTheTime() throws Exception {
ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("MessageIdGenerationTests-context.xml", this.getClass());
GenericXmlApplicationContext childA = new GenericXmlApplicationContext();
@@ -142,7 +142,7 @@ public class MessageIdGenerationTests {
@Test
@Ignore
public void performanceTest(){
public void performanceTest() {
int times = 1000000;
StopWatch watch = new StopWatch();
watch.start();

View File

@@ -20,4 +20,10 @@
<int:channel id="requestChannelBar"/>
<int:channel id="requestChannelBaz"/>
<int:gateway id="customMappedGateway"
service-interface="org.springframework.integration.gateway.GatewayInterfaceTests.Baz"
default-request-channel="requestChannelBaz" mapper="mapper"/>
<bean id="mapper" class="org.springframework.integration.gateway.GatewayInterfaceTests$BazMapper"/>
</beans>

View File

@@ -40,6 +40,7 @@ import org.springframework.integration.channel.DirectChannel;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.MessageBuilder;
/**
* @author Oleg Zhurakousky
@@ -238,6 +239,26 @@ public class GatewayInterfaceTests {
new GatewayProxyFactoryBean(NotAnInterface.class);
}
@Test
public void testWithCustomMapper() {
ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
final AtomicBoolean called = new AtomicBoolean();
MessageHandler handler = new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
assertThat((String) message.getPayload(), equalTo("fizbuz"));
called.set(true);
}
};
channel.subscribe(handler);
Baz baz = ac.getBean(Baz.class);
baz.baz("hello");
assertTrue(called.get());
}
public interface Foo {
@Gateway(requestChannel="requestChannelFoo")
@@ -256,4 +277,18 @@ public class GatewayInterfaceTests {
public static class NotAnInterface {
public void fail(String payload){}
}
public interface Baz {
public void baz(String payload);
}
public static class BazMapper implements MethodArgsMessageMapper {
@Override
public Message<?> toMessage(MethodArgsHolder object) throws Exception {
return MessageBuilder.withPayload("fizbuz").build();
}
}
}

View File

@@ -34,6 +34,7 @@ import org.springframework.messaging.Message;
import org.springframework.integration.annotation.Header;
import org.springframework.integration.annotation.Headers;
import org.springframework.integration.annotation.Payload;
import org.springframework.integration.mapping.MessageMappingException;
import org.springframework.integration.support.MessageBuilder;
/**
@@ -78,7 +79,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests {
assertEquals("bar", message.getHeaders().get("foo"));
}
@Test(expected = IllegalArgumentException.class)
@Test(expected = MessageMappingException.class)
public void toMessageWithPayloadAndRequiredHeaderButNullValue() throws Exception {
Method method = TestService.class.getMethod(
"sendPayloadAndHeader", String.class, String.class);
@@ -134,7 +135,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests {
assertEquals("test", message.getPayload());
}
@Test(expected = IllegalArgumentException.class)
@Test(expected = MessageMappingException.class)
public void toMessageWithPayloadAndHeadersMapWithNonStringKey() throws Exception {
Method method = TestService.class.getMethod(
"sendPayloadAndHeadersMap", String.class, Map.class);
@@ -166,7 +167,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests {
assertEquals("bar", message.getHeaders().get("foo"));
}
@Test(expected = IllegalArgumentException.class)
@Test(expected = MessageMappingException.class)
public void toMessageWithMessageParameterAndRequiredHeaderButNullValue() throws Exception {
Method method = TestService.class.getMethod("sendMessageAndHeader", Message.class, String.class);
GatewayMethodInboundMessageMapper mapper = new GatewayMethodInboundMessageMapper(method);
@@ -197,7 +198,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests {
assertNull(message.getHeaders().get("foo"));
}
@Test(expected = IllegalArgumentException.class)
@Test(expected = MessageMappingException.class)
public void noArgs() throws Exception {
Method method = TestService.class.getMethod("noArgs", new Class<?>[] {});
GatewayMethodInboundMessageMapper mapper = new GatewayMethodInboundMessageMapper(method);
@@ -205,7 +206,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests {
mapper.toMessage(new Object[] {});
}
@Test(expected = IllegalArgumentException.class)
@Test(expected = MessageMappingException.class)
public void onlyHeaders() throws Exception {
Method method = TestService.class.getMethod("onlyHeaders", String.class, String.class);
GatewayMethodInboundMessageMapper mapper = new GatewayMethodInboundMessageMapper(method);

View File

@@ -94,7 +94,7 @@ public class ServiceActivatorDefaultFrameworkMethodTests {
}
@Test
public void testNotOptimizedReplyingMessageHandler() {
public void testOptimizedReplyingMessageHandler() {
QueueChannel replyChannel = new QueueChannel();
Message<?> message = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build();
this.optimizedRefReplyingHandlerTestInputChannel.send(message);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2013 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.
@@ -24,18 +24,17 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.config.ConsumerEndpointFactoryBean;
import org.springframework.messaging.PollableChannel;
import org.springframework.integration.router.AbstractMappingMessageRouter;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Mark Fisher
* @author Gary Russell
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@@ -43,10 +42,10 @@ public class RouterWithMappingTests {
@Autowired
private MessageChannel expressionRouter;
@Autowired
@Qualifier("spelRouter")
private ConsumerEndpointFactoryBean spelRouter;
@Qualifier("spelRouter.handler")
private AbstractMappingMessageRouter spelRouterHandler;
@Autowired
private MessageChannel pojoRouter;
@@ -87,8 +86,7 @@ public class RouterWithMappingTests {
assertNull(fooChannelForExpression.receive(0));
assertNull(barChannelForExpression.receive(0));
// validate dynamics
AbstractMappingMessageRouter router = (AbstractMappingMessageRouter) TestUtils.getPropertyValue(spelRouter, "handler");
router.setChannelMapping("baz", "fooChannelForExpression");
spelRouterHandler.setChannelMapping("baz", "fooChannelForExpression");
expressionRouter.send(message3);
assertNull(defaultChannelForExpression.receive(10));
assertNotNull(fooChannelForExpression.receive(10));

View File

@@ -218,9 +218,7 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
}
@Override
public final void onInit() {
super.onInit();
protected void doInit() {
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());

View File

@@ -46,6 +46,7 @@ public abstract class AbstractRemoteFileInboundChannelAdapterParser extends Abst
// configure the InboundFileSynchronizer properties
IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "remote-directory");
IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "delete-remote-files");
IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "preserve-timestamp");
String remoteFileSeparator = element.getAttribute("remote-file-separator");
synchronizerBuilder.addPropertyValue("remoteFileSeparator", remoteFileSeparator);

View File

@@ -286,8 +286,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
@Override
protected void onInit() {
super.onInit();
protected void doInit() {
Assert.notNull(this.command, "command must not be null");
if (Command.RM.equals(this.command) ||
Command.GET.equals(this.command)) {

View File

@@ -19,7 +19,6 @@ package org.springframework.integration.file.remote.synchronizer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@@ -50,6 +49,7 @@ import org.springframework.util.ObjectUtils;
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileSynchronizer,
@@ -89,6 +89,12 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
*/
private volatile boolean deleteRemoteFiles;
/**
* Should we <em>transfer</em> the remote file <b>timestamp</b>
* to the local file? By default this is false.
*/
private volatile boolean preserveTimestamp;
/**
* Create a synchronizer with the {@link SessionFactory} used to acquire {@link Session} instances.
*/
@@ -127,6 +133,10 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
this.deleteRemoteFiles = deleteRemoteFiles;
}
public void setPreserveTimestamp(boolean preserveTimestamp) {
this.preserveTimestamp = preserveTimestamp;
}
@Override
public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) {
this.evaluationContext = evaluationContext;
@@ -149,7 +159,7 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
Session<F> session = null;
try {
session = this.sessionFactory.getSession();
Assert.state(session != null, "failed to acquire a Session");
Assert.notNull(session, "failed to acquire a Session");
F[] files = session.list(this.remoteDirectory);
if (!ObjectUtils.isEmpty(files)) {
Collection<F> filteredFiles = this.filterFiles(files);
@@ -192,7 +202,6 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
if (!localFile.exists()) {
String tempFileName = localFile.getAbsolutePath() + this.temporaryFileSuffix;
File tempFile = new File(tempFileName);
InputStream inputStream = null;
FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
try {
session.read(remoteFilePath, fileOutputStream);
@@ -206,13 +215,6 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
}
}
finally {
try {
if (inputStream != null) {
inputStream.close();
}
}
catch (Exception ignored1) {
}
try {
fileOutputStream.close();
}
@@ -228,6 +230,9 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
}
}
}
if (this.preserveTimestamp) {
localFile.setLastModified(getModified(remoteFile));
}
}
}
@@ -242,4 +247,6 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
protected abstract String getFilename(F file);
protected abstract long getModified(F file);
}

View File

@@ -78,7 +78,7 @@ public class RemoteFileOutboundGatewayTests {
(sessionFactory, "get", "payload");
gw.setFilter(new TestPatternFilter(""));
try {
gw.onInit();
gw.afterPropertiesSet();
fail("Exception expected");
}
catch (IllegalArgumentException e) {
@@ -93,7 +93,7 @@ public class RemoteFileOutboundGatewayTests {
(sessionFactory, "rm", "payload");
gw.setFilter(new TestPatternFilter(""));
try {
gw.onInit();
gw.afterPropertiesSet();
fail("Exception expected");
}
catch (IllegalArgumentException e) {

View File

@@ -1,2 +1,3 @@
local-test-dir/*.test
remote-target-dir/*test
test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2013 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.
@@ -28,6 +28,7 @@ import org.springframework.integration.file.remote.synchronizer.AbstractInboundF
* @author Iwein Fuld
* @author Josh Long
* @author Mark Fisher
* @author Artem Bilan
* @since 2.0
*/
public class FtpInboundFileSynchronizer extends AbstractInboundFileSynchronizer<FTPFile> {
@@ -50,4 +51,9 @@ public class FtpInboundFileSynchronizer extends AbstractInboundFileSynchronizer<
return (file != null ? file.getName() : null);
}
@Override
protected long getModified(FTPFile file) {
return file.getTimestamp().getTimeInMillis();
}
}

View File

@@ -235,7 +235,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="delete-remote-files" type="xsd:string">
<xsd:attribute name="delete-remote-files" type="xsd:string" default="false">
<xsd:annotation>
<xsd:documentation>
Specify whether to delete the remote source
@@ -245,6 +245,16 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="preserve-timestamp" type="xsd:string" default="false">
<xsd:annotation>
<xsd:documentation>
Specify whether to preserve the modified timestamp from the remote source
file on the local file after copying.
By default, the remote timestamp will NOT be
preserved.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

View File

@@ -3,12 +3,9 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ftp="http://www.springframework.org/schema/integration/ftp"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:ftps="http://www.springframework.org/schema/integration/ftps"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/ftps http://www.springframework.org/schema/integration/ftp/spring-integration-ftps.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/ftp http://www.springframework.org/schema/integration/ftp/spring-integration-ftp.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
http://www.springframework.org/schema/integration/ftp http://www.springframework.org/schema/integration/ftp/spring-integration-ftp.xsd">
<bean id="ftpSessionFactory" class="org.springframework.integration.ftp.session.DefaultFtpSessionFactory">
<property name="host" value="localhost"/>
@@ -19,10 +16,10 @@
<property name="fileType" value="2"/>
</bean>
<ftp:inbound-channel-adapter id="adapterFtpDontAutoCreate"
<ftp:inbound-channel-adapter id="adapterFtpDontAutoCreate"
channel="ftpIn"
session-factory="ftpSessionFactory"
filter="filter"
filter="filter"
local-directory="file:target/bar"
remote-directory="foo/bar"
auto-create-local-directory="false"
@@ -31,9 +28,9 @@
</ftp:inbound-channel-adapter>
<bean id="filter" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.integration.file.entries.EntryListFilter"/>
<constructor-arg value="org.springframework.integration.file.filters.FileListFilter"/>
</bean>
<int:channel id="ftpIn">
<int:queue/>
</int:channel>

View File

@@ -15,19 +15,28 @@
*/
package org.springframework.integration.ftp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.FileNotFoundException;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.MessagingException;
/**
* @author Oleg Zhurakousky
* @author Gunnar Hillert
* @author Artem Bilan
*
*/
public class FtpParserInboundTests {
@@ -43,10 +52,22 @@ public class FtpParserInboundTests {
assertTrue(new File("target/foo").exists());
assertTrue(!new File("target/bar").exists());
}
@Test(expected=BeanCreationException.class)
@Test
public void testLocalFilesAutoCreationFalse() throws Exception{
assertTrue(!new File("target/bar").exists());
new ClassPathXmlApplicationContext("FtpParserInboundTests-fail-context.xml", this.getClass());
try {
new ClassPathXmlApplicationContext("FtpParserInboundTests-fail-context.xml", this.getClass());
fail("BeansException expected.");
}
catch (BeansException e) {
Throwable cause = e.getCause();
assertThat(cause, Matchers.instanceOf(BeanCreationException.class));
cause = cause.getCause();
assertThat(cause, Matchers.instanceOf(MessagingException.class));
cause = cause.getCause();
assertThat(cause, Matchers.instanceOf(FileNotFoundException.class));
assertEquals("bar", cause.getMessage());
}
}
@After

View File

@@ -7,20 +7,21 @@
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/ftp http://www.springframework.org/schema/integration/ftp/spring-integration-ftp.xsd">
<bean id="ftpSessionFactory"
<bean id="ftpSessionFactory"
class="org.springframework.integration.ftp.config.FtpInboundChannelAdapterParserTests.TestSessionFactoryBean"/>
<bean id="csf" class="org.springframework.integration.file.remote.session.CachingSessionFactory">
<constructor-arg ref="ftpSessionFactory"/>
</bean>
<int-ftp:inbound-channel-adapter id="ftpInbound"
channel="ftpChannel"
channel="ftpChannel"
session-factory="ftpSessionFactory"
charset="UTF-8"
auto-create-local-directory="true"
auto-startup="false"
delete-remote-files="true"
preserve-timestamp="true"
filename-pattern="*.txt"
local-directory="."
remote-file-separator=""
@@ -39,7 +40,7 @@
</bean>
<bean id="acceptAllFilter" class="org.springframework.integration.file.filters.AcceptAllFileListFilter" />
<int:transaction-synchronization-factory id="syncFactory">
<int:after-commit expression="'foo'" channel="successChannel"/>
<int:after-rollback expression="'bar'" channel="failureChannel"/>
@@ -48,13 +49,13 @@
<int:channel id="successChannel" />
<int:channel id="failureChannel" />
<bean id="comparator" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="java.util.Comparator"/>
</bean>
<int-ftp:inbound-channel-adapter
channel="ftpChannel"
channel="ftpChannel"
session-factory="ftpSessionFactory"
charset="UTF-8"
auto-create-local-directory="true"
@@ -66,7 +67,7 @@
</int-ftp:inbound-channel-adapter>
<int-ftp:inbound-channel-adapter id="simpleAdapterWithCachedSessions"
channel="ftpChannel"
channel="ftpChannel"
session-factory="csf"
local-directory="."
remote-directory="foo/bar">
@@ -76,7 +77,7 @@
<int:channel id="ftpChannel">
<int:queue/>
</int:channel>
<bean id="entryListFilter" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.integration.file.filters.FileListFilter"/>
</bean>
@@ -89,7 +90,7 @@
</int-ftp:inbound-channel-adapter>
<int:bridge input-channel="autoChannel" output-channel="nullChannel" />
<bean id="transactionManager" class="org.springframework.integration.transaction.PseudoTransactionManager"/>
</beans>

View File

@@ -77,6 +77,7 @@ public class FtpInboundChannelAdapterParserTests {
FtpInboundFileSynchronizer fisync =
(FtpInboundFileSynchronizer) TestUtils.getPropertyValue(inbound, "synchronizer");
assertNotNull(TestUtils.getPropertyValue(fisync, "localFilenameGeneratorExpression"));
assertTrue(TestUtils.getPropertyValue(fisync, "preserveTimestamp", Boolean.class));
assertEquals(".foo", TestUtils.getPropertyValue(fisync, "temporaryFileSuffix", String.class));
String remoteFileSeparator = (String) TestUtils.getPropertyValue(fisync, "remoteFileSeparator");
assertNotNull(remoteFileSeparator);

View File

@@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -30,10 +31,12 @@ import static org.mockito.Mockito.when;
import java.io.File;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Test;
import org.mockito.Mockito;
@@ -51,6 +54,7 @@ import org.springframework.messaging.Message;
* @author Oleg Zhurakousky
* @author Gunnar Hillert
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public class FtpInboundRemoteFileSystemSynchronizerTests {
@@ -80,6 +84,7 @@ public class FtpInboundRemoteFileSystemSynchronizerTests {
ftpSessionFactory.setHost("foo.com");
FtpInboundFileSynchronizer synchronizer = spy(new FtpInboundFileSynchronizer(ftpSessionFactory));
synchronizer.setDeleteRemoteFiles(true);
synchronizer.setPreserveTimestamp(true);
synchronizer.setRemoteDirectory("remote-test-dir");
synchronizer.setFilter(new FtpRegexPatternFileListFilter(".*\\.test$"));
synchronizer.setIntegrationEvaluationContext(ExpressionUtils.createStandardEvaluationContext());
@@ -98,9 +103,15 @@ public class FtpInboundRemoteFileSystemSynchronizerTests {
Message<File> atestFile = ms.receive();
assertNotNull(atestFile);
assertEquals("A.TEST.a", atestFile.getPayload().getName());
// The test remote files are created with the current timestamp + 1 day.
assertThat(atestFile.getPayload().lastModified(), Matchers.greaterThan(System.currentTimeMillis()));
Message<File> btestFile = ms.receive();
assertNotNull(btestFile);
assertEquals("B.TEST.a", btestFile.getPayload().getName());
// The test remote files are created with the current timestamp + 1 day.
assertThat(atestFile.getPayload().lastModified(), Matchers.greaterThan(System.currentTimeMillis()));
Message<File> nothing = ms.receive();
assertNull(nothing);
@@ -127,6 +138,9 @@ public class FtpInboundRemoteFileSystemSynchronizerTests {
FTPFile file = new FTPFile();
file.setName(fileName);
file.setType(FTPFile.FILE_TYPE);
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, 1);
file.setTimestamp(calendar);
ftpFiles.add(file);
when(ftpClient.retrieveFile(Mockito.eq("remote-test-dir/" + fileName) , Mockito.any(OutputStream.class))).thenReturn(true);
}

View File

@@ -13,9 +13,6 @@
package org.springframework.integration.groovy;
import groovy.lang.Binding;
import groovy.lang.GString;
import java.util.Map;
import org.springframework.messaging.Message;
@@ -29,6 +26,9 @@ import org.springframework.scripting.support.StaticScriptSource;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import groovy.lang.Binding;
import groovy.lang.GString;
/**
* @author Dave Syer
* @author Mark Fisher
@@ -109,11 +109,11 @@ public class GroovyCommandMessageProcessor extends AbstractScriptExecutingMessag
customizerDecorator.setVariables(variables);
}
GroovyScriptFactory factory = new GroovyScriptFactory(this.getClass().getSimpleName(), customizerDecorator);
if (getBeanClassLoader() != null) {
factory.setBeanClassLoader(getBeanClassLoader());
if (this.beanClassLoader != null) {
factory.setBeanClassLoader(this.beanClassLoader);
}
if (getBeanFactory() != null) {
factory.setBeanFactory(getBeanFactory());
if (this.beanFactory != null) {
factory.setBeanFactory(this.beanFactory);
}
Object result = factory.getScriptedObject(scriptSource, null);
return (result instanceof GString) ? result.toString() : result;

View File

@@ -16,36 +16,52 @@
package org.springframework.integration.groovy;
import groovy.lang.Binding;
import groovy.lang.GString;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import groovy.lang.Script;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.messaging.Message;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.integration.scripting.AbstractScriptExecutingMessageProcessor;
import org.springframework.integration.scripting.ScriptVariableGenerator;
import org.springframework.messaging.Message;
import org.springframework.scripting.ScriptCompilationException;
import org.springframework.scripting.ScriptSource;
import org.springframework.scripting.groovy.GroovyObjectCustomizer;
import org.springframework.scripting.groovy.GroovyScriptFactory;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ClassUtils;
/**
* The {@link org.springframework.integration.handler.MessageProcessor} implementation
* to evaluate Groovy scripts.
*
* @author Dave Syer
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Stefan Reuter
* @author Artem Bilan
* @since 2.0
*/
public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecutingMessageProcessor<Object> implements InitializingBean {
public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecutingMessageProcessor<Object> {
private final GroovyScriptFactory scriptFactory;
private final VariableBindingGroovyObjectCustomizerDecorator customizerDecorator =
new VariableBindingGroovyObjectCustomizerDecorator();
private final VariableBindingGroovyObjectCustomizerDecorator
customizerDecorator = new VariableBindingGroovyObjectCustomizerDecorator();
private final Lock scriptLock = new ReentrantLock();
private volatile ScriptSource scriptSource;
private volatile GroovyClassLoader groovyClassLoader = new GroovyClassLoader(ClassUtils.getDefaultClassLoader());
private volatile Class<?> scriptClass;
/**
* Create a processor for the given {@link ScriptSource} that will use a
@@ -54,7 +70,6 @@ public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecuti
public GroovyScriptExecutingMessageProcessor(ScriptSource scriptSource) {
super();
this.scriptSource = scriptSource;
this.scriptFactory = new GroovyScriptFactory(this.getClass().getSimpleName(), this.customizerDecorator);
}
/**
@@ -64,9 +79,21 @@ public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecuti
public GroovyScriptExecutingMessageProcessor(ScriptSource scriptSource, ScriptVariableGenerator scriptVariableGenerator) {
super(scriptVariableGenerator);
this.scriptSource = scriptSource;
this.scriptFactory = new GroovyScriptFactory(this.getClass().getSimpleName(), this.customizerDecorator);
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
super.setBeanClassLoader(classLoader);
this.groovyClassLoader = new GroovyClassLoader(classLoader);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
super.setBeanFactory(beanFactory);
if (beanFactory instanceof ConfigurableListableBeanFactory) {
((ConfigurableListableBeanFactory) beanFactory).ignoreDependencyType(MetaClass.class);
}
}
/**
* Sets a {@link GroovyObjectCustomizer} for this processor.
@@ -83,22 +110,58 @@ public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecuti
@Override
protected Object executeScript(ScriptSource scriptSource, Map<String, Object> variables) throws Exception {
Assert.notNull(scriptSource, "scriptSource must not be null");
synchronized (this) {
if (!CollectionUtils.isEmpty(variables)) {
this.customizerDecorator.setVariables(variables);
this.parseScriptIfNecessary(scriptSource);
Object result = this.execute(variables);
return (result instanceof GString) ? result.toString() : result;
}
private void parseScriptIfNecessary(ScriptSource scriptSource) throws Exception {
if (this.scriptClass == null || scriptSource.isModified()) {
this.scriptLock.lockInterruptibly();
try {
// synchronized double check
if (this.scriptClass == null || scriptSource.isModified()) {
this.scriptClass = this.groovyClassLoader.parseClass(
scriptSource.getScriptAsString(), scriptSource.suggestedClassName());
}
}
finally {
this.scriptLock.unlock();
}
Object result = this.scriptFactory.getScriptedObject(scriptSource, null);
return (result instanceof GString) ? result.toString() : result;
}
}
@Override
public void afterPropertiesSet() throws Exception {
if (getBeanClassLoader() != null) {
this.scriptFactory.setBeanClassLoader(getBeanClassLoader());
private Object execute(Map<String, Object> variables) throws ScriptCompilationException {
try {
GroovyObject goo = (GroovyObject) this.scriptClass.newInstance();
GroovyObjectCustomizer groovyObjectCustomizer = this.customizerDecorator;
if (variables != null) {
// Override empty Script.Binding with new one with 'variables'
groovyObjectCustomizer = new BindingOverwriteGroovyObjectCustomizerDecorator(new Binding(variables));
((VariableBindingGroovyObjectCustomizerDecorator) groovyObjectCustomizer).setCustomizer(this.customizerDecorator);
}
if (goo instanceof Script) {
// Allow metaclass and other customization.
groovyObjectCustomizer.customize(goo);
// A Groovy script, probably creating an instance: let's execute it.
return ((Script) goo).run();
}
else {
// An instance of the scripted class: let's return it as-is.
return goo;
}
}
if (getBeanFactory() != null) {
this.scriptFactory.setBeanFactory(getBeanFactory());
catch (InstantiationException ex) {
throw new ScriptCompilationException(
this.scriptSource, "Could not instantiate Groovy script class: " + this.scriptClass.getName(), ex);
}
catch (IllegalAccessException ex) {
throw new ScriptCompilationException(
this.scriptSource, "Could not access Groovy script constructor: " + this.scriptClass.getName(), ex);
}
}
}

View File

@@ -1,213 +0,0 @@
/*
* Copyright 2002-2010 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
*
* http://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.groovy;
import static org.junit.Assert.assertEquals;
import groovy.lang.GroovyObject;
import groovy.lang.Script;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Executors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.scripting.groovy.GroovyObjectCustomizer;
import org.springframework.scripting.groovy.GroovyScriptFactory;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.util.Assert;
/**
* @author Dave Syer
*/
public class GroovyExpressionTests {
private static Log logger = LogFactory.getLog(GroovyExpressionTests.class);
@Before
public void setLogLevel() {
LogManager.getLogger(getClass()).setLevel(Level.DEBUG);
}
@After
public void resetLogLevel() {
LogManager.getLogger(getClass()).setLevel(Level.INFO);
}
@Test
public void testScriptFactoryCustomizer() throws Exception {
Customizer customizer = new Customizer(Collections.singletonMap("name", (Object) "foo"));
GroovyScriptFactory factory = new GroovyScriptFactory("Groovy Script", customizer);
ResourceScriptSource scriptSource = new ResourceScriptSource(new NamedByteArrayResource("\"name=${name}\"".getBytes(), "InlineScript"));
Object scriptedObject = factory.getScriptedObject(scriptSource, null);
assertEquals("name=foo", scriptedObject.toString());
customizer.setMap(Collections.singletonMap("name", (Object) "bar"));
scriptedObject = factory.getScriptedObject(scriptSource, null);
assertEquals("name=bar", scriptedObject.toString());
}
@Test
public void testScriptFactoryCustomizerThreadSafety() throws Exception {
final Customizer customizer = new Customizer(Collections.singletonMap("name", (Object) "foo"));
final GroovyScriptFactory factory = new GroovyScriptFactory("Groovy Script", customizer);
final ResourceScriptSource scriptSource = new ResourceScriptSource(new NamedByteArrayResource(
"\"name=${name}\"".getBytes(), "InlineScript"));
Object scriptedObject = factory.getScriptedObject(scriptSource, null);
assertEquals("name=foo", scriptedObject.toString());
CompletionService<String> completionService = new ExecutorCompletionService<String>(Executors.newFixedThreadPool(10));
for (int i = 0; i < 100; i++) {
final String name = "bar" + i;
completionService.submit(new Callable<String>() {
public String call() throws Exception {
Object scriptedObject;
synchronized (customizer) {
customizer.setMap(Collections.singletonMap("name", (Object) name));
scriptedObject = factory.getScriptedObject(scriptSource, null);
}
String result = scriptedObject.toString();
logger.debug("Result=" + result + " with name=" + name);
if (!("name=" + name).equals(result)) {
throw new IllegalStateException("Wrong value (" + result + ") for: " + name);
}
return name;
}
});
}
Set<String> set = new HashSet<String>();
for (int i = 0; i < 100; i++) {
set.add(completionService.take().get());
}
assertEquals(100, set.size());
}
@Test
public void testScriptFactoryCustomizerStatic() throws Exception {
final Customizer customizer = new Customizer(Collections.singletonMap("name", (Object) "foo"));
final GroovyScriptFactory factory = new GroovyScriptFactory("Groovy Script", customizer);
final ResourceScriptSource scriptSource = new ResourceScriptSource(new NamedByteArrayResource(
"\"name=${name}\"".getBytes(), "InlineScript"));
Object scriptedObject = factory.getScriptedObject(scriptSource, null);
assertEquals("name=foo", scriptedObject.toString());
CompletionService<String> completionService = new ExecutorCompletionService<String>(Executors.newFixedThreadPool(10));
for (int i = 0; i < 100; i++) {
final String name = "bar" + i;
completionService.submit(new Callable<String>() {
public String call() throws Exception {
Object scriptedObject = factory.getScriptedObject(scriptSource, null);
String result = scriptedObject.toString();
logger.debug("Result=" + result + " with name=" + name);
if (!("name=foo").equals(result)) {
throw new IllegalStateException("Wrong value (" + result + ") for: " + name);
}
return name;
}
});
}
Set<String> set = new HashSet<String>();
for (int i = 0; i < 100; i++) {
set.add(completionService.take().get());
}
assertEquals(100, set.size());
}
@Test
public void testScriptFactoryCustomizerThreadSafetyWithNewScript() throws Exception {
final Customizer customizer = new Customizer(Collections.singletonMap("name", (Object) "foo"));
final GroovyScriptFactory factory = new GroovyScriptFactory("Groovy Script", customizer);
CompletionService<String> completionService = new ExecutorCompletionService<String>(Executors.newFixedThreadPool(5));
for (int i = 0; i < 100; i++) {
final String name = "Bar" + i;
completionService.submit(new Callable<String>() {
public String call() throws Exception {
Object scriptedObject;
synchronized (customizer) {
customizer.setMap(Collections.singletonMap("name", (Object) name));
ResourceScriptSource scriptSource = new ResourceScriptSource(new NamedByteArrayResource(
"\"name=${name}\"".getBytes(), "InlineScript" + name));
scriptedObject = factory.getScriptedObject(scriptSource, null);
}
String result = scriptedObject.toString();
logger.debug("Result=" + result + " with name=" + name);
if (!("name=" + name).equals(result)) {
throw new IllegalStateException("Wrong value (" + result + ") for: " + name);
}
return name;
}
});
}
Set<String> set = new HashSet<String>();
for (int i = 0; i < 100; i++) {
set.add(completionService.take().get());
}
assertEquals(100, set.size());
}
private static class Customizer implements GroovyObjectCustomizer {
private Map<String, Object> map = new HashMap<String, Object>();
public Customizer(Map<String, Object> map) {
super();
this.map.putAll(map);
}
public void customize(GroovyObject goo) {
Assert.state(goo instanceof Script, "Expected a Script");
for (Map.Entry<String, Object> entry : map.entrySet()) {
((Script) goo).getBinding().setVariable(entry.getKey(), entry.getValue());
}
}
public void setMap(Map<String, Object> map) {
this.map.clear();
this.map.putAll(map);
}
}
private static class NamedByteArrayResource extends ByteArrayResource {
private final String fileName;
public NamedByteArrayResource(byte[] bytes, String fileName) {
super(bytes);
this.fileName = fileName;
}
@Override
public String getFilename() throws IllegalStateException {
return fileName;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,26 +18,35 @@ package org.springframework.integration.groovy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.core.io.AbstractResource;
import org.springframework.messaging.Message;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.scripting.RefreshableResourceScriptSource;
import org.springframework.integration.scripting.ScriptVariableGenerator;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.scripting.ScriptSource;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.scripting.support.StaticScriptSource;
import org.springframework.test.annotation.Repeat;
import groovy.lang.Script;
/**
* @author Mark Fisher
* @author Dave Syer
@@ -176,6 +185,43 @@ public class GroovyScriptExecutingMessageProcessorTests {
assertEquals("payload is 'hello'", result.toString());
}
@Test
public void testInt3166GroovyScriptExecutingMessageProcessorPerformance() throws Exception {
final Message<?> message = new GenericMessage<Object>("test");
final AtomicInteger var1 = new AtomicInteger();
final AtomicInteger var2 = new AtomicInteger();
String script =
"var1.incrementAndGet(); Thread.sleep(100); var2.set(Math.max(var1.get(), var2.get())); var1.decrementAndGet()";
ScriptSource scriptSource = new StaticScriptSource(script, Script.class.getName());
final MessageProcessor<Object> processor =
new GroovyScriptExecutingMessageProcessor(scriptSource, new ScriptVariableGenerator() {
@Override
public Map<String, Object> generateScriptVariables(Message<?> message) {
Map<String, Object> variables = new HashMap<String, Object>(2);
variables.put("var1", var1);
variables.put("var2", var2);
return variables;
}
});
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
processor.processMessage(message);
}
});
}
executor.shutdown();
assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
assertTrue(var2.get() > 1);
}
private static class TestResource extends AbstractResource {

View File

@@ -16,9 +16,9 @@
package org.springframework.integration.groovy.config;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import groovy.lang.GroovyObject;
@@ -30,6 +30,7 @@ import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanCreationNotAllowedException;
import org.springframework.beans.factory.BeanIsAbstractException;
@@ -115,6 +116,8 @@ public class GroovyControlBusTests {
Message<?> message = MessageBuilder.withPayload("def result = requestScopedService.convert('testString')").build();
this.input.send(message);
assertEquals("cat", output.receive(0).getPayload());
RequestContextHolder.resetRequestAttributes();
}
@Test //INT-2567
@@ -180,7 +183,7 @@ public class GroovyControlBusTests {
private static class MockRequestAttributes implements RequestAttributes {
private Map<String, Object> fakeRequest = new HashMap<String, Object>();
private final Map<String, Object> fakeRequest = new HashMap<String, Object>();
public Object getAttribute(String name, int scope) {
return fakeRequest.get(name);

View File

@@ -97,6 +97,7 @@ public class GroovyFilterTests {
assertTrue(this.groovyFilterMessageHandler instanceof MessageFilter);
MessageSelector selector = TestUtils.getPropertyValue(this.groovyFilterMessageHandler, "selector",
MethodInvokingSelector.class);
@SuppressWarnings("rawtypes")
MessageProcessor messageProcessor = TestUtils.getPropertyValue(selector, "messageProcessor", MessageProcessor.class);
//before it was MethodInvokingMessageProcessor
assertTrue(messageProcessor instanceof GroovyScriptExecutingMessageProcessor);

View File

@@ -107,6 +107,7 @@ public class GroovyRouterTests {
@Test
public void testInt2433VerifyRiddingOfMessageProcessorsWrapping() {
assertTrue(this.groovyRouterMessageHandler instanceof MethodInvokingRouter);
@SuppressWarnings("rawtypes")
MessageProcessor messageProcessor = TestUtils.getPropertyValue(this.groovyRouterMessageHandler,
"messageProcessor", MessageProcessor.class);
//before it was MethodInvokingMessageProcessor

View File

@@ -90,7 +90,6 @@ public class GroovyServiceActivatorTests {
String value1 = (String) replyChannel.receive(0).getPayload();
String value2 = (String) replyChannel.receive(0).getPayload();
String value3 = (String) replyChannel.receive(0).getPayload();
System.out.println(value1 + "\n" + value2 + "\n" + value3);
assertTrue(value1.startsWith("groovy-test-1-foo - bar"));
assertTrue(value2.startsWith("groovy-test-2-foo - bar"));
assertTrue(value3.startsWith("groovy-test-3-foo - bar"));

View File

@@ -82,10 +82,11 @@ public class GroovySplitterTests {
@Test
public void testInt2433VerifyRiddingOfMessageProcessorsWrapping() {
assertTrue(this.groovySplitterMessageHandler instanceof MethodInvokingSplitter);
MessageProcessor messageProcessor = TestUtils.getPropertyValue(this.groovySplitterMessageHandler,
assertTrue(this.groovySplitterMessageHandler instanceof MethodInvokingSplitter);
@SuppressWarnings("rawtypes")
MessageProcessor messageProcessor = TestUtils.getPropertyValue(this.groovySplitterMessageHandler,
"messageProcessor", MessageProcessor.class);
//before it was MethodInvokingMessageProcessor
//before it was MethodInvokingMessageProcessor
assertTrue(messageProcessor instanceof GroovyScriptExecutingMessageProcessor);
}

View File

@@ -88,11 +88,12 @@ public class GroovyTransformerTests {
@Test
public void testInt2433VerifyRiddingOfMessageProcessorsWrapping() {
assertTrue(this.groovyTransformerMessageHandler instanceof MessageTransformingHandler);
Transformer transformer = TestUtils.getPropertyValue(this.groovyTransformerMessageHandler, "transformer", Transformer.class);
assertTrue(transformer instanceof AbstractMessageProcessingTransformer);
MessageProcessor messageProcessor = TestUtils.getPropertyValue(transformer, "messageProcessor", MessageProcessor.class);
//before it was MethodInvokingMessageProcessor
assertTrue(this.groovyTransformerMessageHandler instanceof MessageTransformingHandler);
Transformer transformer = TestUtils.getPropertyValue(this.groovyTransformerMessageHandler, "transformer", Transformer.class);
assertTrue(transformer instanceof AbstractMessageProcessingTransformer);
@SuppressWarnings("rawtypes")
MessageProcessor messageProcessor = TestUtils.getPropertyValue(transformer, "messageProcessor", MessageProcessor.class);
//before it was MethodInvokingMessageProcessor
assertTrue(messageProcessor instanceof GroovyScriptExecutingMessageProcessor);
}
}

View File

@@ -19,10 +19,12 @@ package org.springframework.integration.http.inbound;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -65,6 +67,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.servlet.DispatcherServlet;
@@ -210,8 +213,15 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
/**
* Specifies a SpEL expression to evaluate in order to generate the Message payload.
* The EvaluationContext will be populated with an HttpEntity instance as the root object,
* and it may contain one or both of the <code>#pathVariables</code> and
* <code>#queryParameters</code> variables if present. Those variables' values are Maps.
* and it may contain variables:
* <ul>
* <li><code>#pathVariables</code></li>
* <li><code>#requestParams</code></li>
* <li><code>#requestAttributes</code></li>
* <li><code>#requestHeaders</code></li>
* <li><code>#matrixVariables</code></li>
* <li><code>#cookies</code>
* </ul>
*/
public void setPayloadExpression(Expression payloadExpression) {
this.payloadExpression = payloadExpression;
@@ -221,8 +231,15 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
* Specifies a Map of SpEL expressions to evaluate in order to generate the Message headers.
* The keys in the map will be used as the header names. When evaluating the expression,
* the EvaluationContext will be populated with an HttpEntity instance as the root object,
* and it may contain one or both of the <code>#pathVariables</code> and
* <code>#queryParameters</code> variables if present. Those variables' values are Maps.
* and it may contain variables:
* <ul>
* <li><code>#pathVariables</code></li>
* <li><code>#requestParams</code></li>
* <li><code>#requestAttributes</code></li>
* <li><code>#requestHeaders</code></li>
* <li><code>#matrixVariables</code></li>
* <li><code>#cookies</code>
* </ul>
*/
public void setHeaderExpressions(Map<String, Expression> headerExpressions) {
this.headerExpressions = headerExpressions;
@@ -379,9 +396,22 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
StandardEvaluationContext evaluationContext = this.createEvaluationContext();
evaluationContext.setRootObject(httpEntity);
evaluationContext.setVariable("requestAttributes", RequestContextHolder.currentRequestAttributes());
MultiValueMap<String, String> requestParams = this.convertParameterMap(servletRequest.getParameterMap());
evaluationContext.setVariable("requestParams", requestParams);
evaluationContext.setVariable("requestHeaders", new ServletServerHttpRequest(servletRequest).getHeaders());
Cookie[] requestCookies = servletRequest.getCookies();
if (!ObjectUtils.isEmpty(requestCookies)) {
Map<String, Cookie> cookies = new HashMap<String, Cookie>(requestCookies.length);
for (Cookie requestCookie : requestCookies) {
cookies.put(requestCookie.getName(), requestCookie);
}
evaluationContext.setVariable("cookies", cookies);
}
Map<String, String> pathVariables =
(Map<String, String>) servletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
@@ -392,6 +422,17 @@ public abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewa
evaluationContext.setVariable("pathVariables", pathVariables);
}
//TODO change it to HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE after upgrade to Spring 4.0
Map<String, MultiValueMap<String, String>> matrixVariables =
(Map<String, MultiValueMap<String, String>>) servletRequest.getAttribute(HandlerMapping.class.getName() + ".matrixVariables");
if (!CollectionUtils.isEmpty(matrixVariables)) {
if (logger.isDebugEnabled()) {
logger.debug("Mapped matrix variables: " + matrixVariables);
}
evaluationContext.setVariable("matrixVariables", matrixVariables);
}
Map<String, Object> headers = this.headerMapper.toHeaders(request.getHeaders());
Object payload = null;
if (this.payloadExpression != null) {

View File

@@ -291,8 +291,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
}
@Override
public void onInit() {
super.onInit();
protected void doInit() {
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
ConversionService conversionService = this.getConversionService();

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2013 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
*
* http://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.http;
import org.junit.After;
import org.junit.Before;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* @author Artem Bilan
* @since 3.0
*/
public abstract class AbstractHttpInboundTests {
@Before
public void setupHttpInbound() {
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest()));
}
@After
public void tearDownHttpInbound() {
RequestContextHolder.resetRequestAttributes();
}
}

View File

@@ -137,6 +137,7 @@ public class HttpProxyScenarioTests {
assertEquals(ifModifiedSince, headers.get("If-Modified-Since"));
assertEquals(ifUnmodifiedSince, headers.get("If-Unmodified-Since"));
RequestContextHolder.resetRequestAttributes();
}
}

View File

@@ -18,11 +18,11 @@ package org.springframework.integration.http.config;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
@@ -36,11 +36,9 @@ import javax.servlet.http.HttpServletResponse;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.converter.HttpMessageConverter;
@@ -48,6 +46,7 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.integration.history.MessageHistory;
import org.springframework.integration.http.AbstractHttpInboundTests;
import org.springframework.integration.http.converter.SerializingHttpMessageConverter;
import org.springframework.integration.http.inbound.HttpRequestHandlingController;
import org.springframework.integration.http.inbound.HttpRequestHandlingMessagingGateway;
@@ -74,7 +73,7 @@ import org.springframework.web.servlet.HandlerMapping;
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class HttpInboundChannelAdapterParserTests {
public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTests {
@Autowired
private PollableChannel requests;

View File

@@ -39,6 +39,7 @@ import org.springframework.messaging.Message;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.http.AbstractHttpInboundTests;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@@ -54,7 +55,7 @@ import org.springframework.web.servlet.View;
* @author Biju Kunjummen
* @since 2.0
*/
public class HttpRequestHandlingControllerTests {
public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests {
@Test
public void sendOnly() throws Exception {

View File

@@ -16,9 +16,9 @@
package org.springframework.integration.http.inbound;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import java.io.IOException;
@@ -43,6 +43,7 @@ import org.springframework.messaging.Message;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.http.AbstractHttpInboundTests;
import org.springframework.integration.http.converter.SerializingHttpMessageConverter;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.mock.web.MockHttpServletRequest;
@@ -58,7 +59,7 @@ import org.springframework.util.SerializationUtils;
* @author Biju Kunjummen
* @since 2.0
*/
public class HttpRequestHandlingMessagingGatewayTests {
public class HttpRequestHandlingMessagingGatewayTests extends AbstractHttpInboundTests {
@Test
@SuppressWarnings("unchecked")

View File

@@ -27,11 +27,12 @@ import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.http.AbstractHttpInboundTests;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessagingException;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.util.AntPathMatcher;
@@ -45,7 +46,7 @@ import org.springframework.web.servlet.HandlerMapping;
* @author Artem Bilan
* @author Biju Kunjummen
*/
public class HttpRequestHandlingMessagingGatewayWithPathMappingTests {
public class HttpRequestHandlingMessagingGatewayWithPathMappingTests extends AbstractHttpInboundTests {
private static ExpressionParser PARSER = new SpelExpressionParser();

View File

@@ -26,8 +26,16 @@
request-channel="toLowerCaseChannel"
payload-expression="#pathVariables.value">
<int-http:request-mapping headers="toLowerCase"/>
<int-http:header name="requestAttributes" expression="#requestAttributes"/>
<int-http:header name="requestParams" expression="#requestParams"/>
<int-http:header name="requestHeaders" expression="#requestHeaders"/>
<int-http:header name="matrixVariables" expression="#matrixVariables"/>
<int-http:header name="cookies" expression="#cookies"/>
</int-http:inbound-gateway>
<int:publish-subscribe-channel id="toLowerCaseChannel"/>
<int:transformer input-channel="toLowerCaseChannel" expression="payload.toLowerCase()"/>
<int-http:inbound-gateway path="#{TEST_PATH}"

View File

@@ -17,22 +17,37 @@
package org.springframework.integration.http.inbound;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.Cookie;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.integration.Message;
import org.springframework.integration.MessageHeaders;
import org.springframework.integration.MessagingException;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.core.SubscribableChannel;
import org.springframework.integration.http.AbstractHttpInboundTests;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
@@ -44,7 +59,7 @@ import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
//INT-2312
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class Int2312RequestMappingIntegrationTests {
public class Int2312RequestMappingIntegrationTests extends AbstractHttpInboundTests {
public static final String TEST_PATH = "/test/{value}";
@@ -53,6 +68,9 @@ public class Int2312RequestMappingIntegrationTests {
@Autowired
private HandlerMapping handlerMapping;
@Autowired
private SubscribableChannel toLowerCaseChannel;
private HandlerAdapter handlerAdapter = new HttpRequestHandlerAdapter();
@Test
@@ -77,26 +95,63 @@ public class Int2312RequestMappingIntegrationTests {
@Test
@SuppressWarnings("unchecked")
//INT-1362
public void testURIVariablesAndHeaders() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET");
String testRequest = "aBc";
// TODO test it after upgrade to Spring 4.0
// String testRequest = "aBc;q1=1;q2=2";
String requestURI = "/test/" + testRequest;
request.setRequestURI(requestURI);
request.setContentType("text/plain");
final Map<String, String> params = new HashMap<String, String>();
params.put("foo", "bar");
request.setParameters(params);
request.setContent("hello".getBytes());
final Cookie cookie = new Cookie("foo", "bar");
request.setCookies(cookie);
request.addHeader("toLowerCase", true);
//See org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch
Map<String, String> uriTemplateVariables =
new AntPathMatcher().extractUriTemplateVariables(TEST_PATH, requestURI);
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
//See org.springframework.web.servlet.FrameworkServlet#initContextHolders
final RequestAttributes attributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(attributes);
this.toLowerCaseChannel.subscribe(new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
MessageHeaders headers = message.getHeaders();
assertEquals(attributes, headers.get("requestAttributes"));
Object requestParams = headers.get("requestParams");
assertNotNull(requestParams);
assertEquals(params, ((MultiValueMap<String, String>) requestParams).toSingleValueMap());
// TODO test it after upgrade to Spring 4.0
// assertEquals(matrixVariables, headers.get("matrixVariables"));
Object requestHeaders = headers.get("requestHeaders");
assertNotNull(requestParams);
assertEquals(MediaType.TEXT_PLAIN, ((HttpHeaders) requestHeaders).getContentType());
Map<String, Cookie> cookies = (Map<String, Cookie>) headers.get("cookies");
assertEquals(1, cookies.size());
Cookie foo = cookies.get("foo");
assertNotNull(foo);
assertEquals(cookie, foo);
}
});
MockHttpServletResponse response = new MockHttpServletResponse();
request.addHeader("toLowerCase", true);
Object handler = this.handlerMapping.getHandler(request).getHandler();
this.handlerAdapter.handle(request, response, handler);
final String testResponse = response.getContentAsString();
assertEquals(testRequest.toLowerCase(), testResponse);
RequestContextHolder.resetRequestAttributes();
}
@Test

View File

@@ -17,17 +17,22 @@
package org.springframework.integration.ip.tcp.connection;
import java.net.Socket;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Abstract class for client connection factories; client connection factories
* establish outgoing connections.
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*
*/
public abstract class AbstractClientConnectionFactory extends AbstractConnectionFactory {
private TcpConnectionSupport theConnection;
private final ReadWriteLock theConnectionLock = new ReentrantReadWriteLock();
private volatile TcpConnectionSupport theConnection;
/**
* Constructs a factory that will established connections to the host and port.
@@ -45,18 +50,70 @@ public abstract class AbstractClientConnectionFactory extends AbstractConnection
*/
public TcpConnectionSupport getConnection() throws Exception {
this.checkActive();
if (this.isSingleUse()) {
return obtainConnection();
} else {
synchronized(this) {
TcpConnectionSupport connection = obtainConnection();
this.setTheConnection(connection);
return this.obtainConnection();
}
protected TcpConnectionSupport obtainConnection() throws Exception {
if (!this.isSingleUse()) {
TcpConnectionSupport connection = this.obtainSharedConnection();
if (connection != null) {
return connection;
}
}
return this.obtainNewConnection();
}
protected final TcpConnectionSupport obtainSharedConnection() throws InterruptedException {
this.theConnectionLock.readLock().lockInterruptibly();
try {
TcpConnectionSupport theConnection = this.getTheConnection();
if (theConnection != null && theConnection.isOpen()) {
return theConnection;
}
}
finally {
this.theConnectionLock.readLock().unlock();
}
return null;
}
protected final TcpConnectionSupport obtainNewConnection() throws Exception {
boolean singleUse = this.isSingleUse();
if (!singleUse) {
this.theConnectionLock.writeLock().lockInterruptibly();
}
try {
TcpConnectionSupport connection;
if (!singleUse) {
// Another write lock holder might have created a new one by now.
connection = this.obtainSharedConnection();
if (connection != null) {
return connection;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Opening new socket connection to " + this.getHost() + ":" + this.getPort());
}
connection = this.buildNewConnection();
if (!singleUse) {
this.setTheConnection(connection);
}
connection.publishConnectionOpenEvent();
return connection;
}
finally {
if (!singleUse) {
this.theConnectionLock.writeLock().unlock();
}
}
}
protected abstract TcpConnectionSupport obtainConnection() throws Exception;
protected TcpConnectionSupport buildNewConnection() throws Exception {
throw new UnsupportedOperationException("Factories that don't override this class' obtainConnection() must implement this method");
}
/**
* Transfers attributes such as (de)serializers, singleUse etc to a new connection.

View File

@@ -127,7 +127,6 @@ public abstract class TcpConnectionSupport implements TcpConnection {
if (connectionFactoryName != null) {
this.connectionFactoryName = connectionFactoryName;
}
this.publishConnectionOpenEvent();
if (logger.isDebugEnabled()) {
logger.debug("New connection " + this.getConnectionId());
}

View File

@@ -44,20 +44,8 @@ public class TcpNetClientConnectionFactory extends
super(host, port);
}
/**
* @throws IOException
* @throws SocketException
* @throws Exception
*/
@Override
protected TcpConnectionSupport obtainConnection() throws Exception {
TcpConnectionSupport theConnection = this.getTheConnection();
if (theConnection != null && theConnection.isOpen()) {
return theConnection;
}
if (logger.isDebugEnabled()) {
logger.debug("Opening new socket connection to " + this.getHost() + ":" + this.getPort());
}
protected TcpConnectionSupport buildNewConnection() throws IOException, SocketException, Exception {
Socket socket = createSocket(this.getHost(), this.getPort());
setSocketAttributes(socket);
TcpConnectionSupport connection = new TcpNetConnection(socket, false, this.isLookupHost(),

View File

@@ -64,7 +64,8 @@ public class TcpNetServerConnectionFactory extends AbstractServerConnectionFacto
try {
if (this.getLocalAddress() == null) {
theServerSocket = createServerSocket(this.getPort(), this.getBacklog(), null);
} else {
}
else {
InetAddress whichNic = InetAddress.getByName(this.getLocalAddress());
theServerSocket = createServerSocket(this.getPort(), this.getBacklog(), whichNic);
}
@@ -80,7 +81,8 @@ public class TcpNetServerConnectionFactory extends AbstractServerConnectionFacto
*/
try {
socket = serverSocket.accept();
} catch (SocketTimeoutException ste) {
}
catch (SocketTimeoutException ste) {
if (logger.isDebugEnabled()) {
logger.debug("Timed out on accept; continuing");
}
@@ -104,9 +106,11 @@ public class TcpNetServerConnectionFactory extends AbstractServerConnectionFacto
this.initializeConnection(connection, socket);
this.getTaskExecutor().execute(connection);
this.harvestClosedConnections();
connection.publishConnectionOpenEvent();
}
}
} catch (Exception e) {
}
catch (Exception e) {
// don't log an error if we had a good socket once and now it's closed
if (e instanceof SocketException && theServerSocket != null) {
logger.warn("Server Socket closed");

View File

@@ -18,7 +18,6 @@ package org.springframework.integration.ip.tcp.connection;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
@@ -61,13 +60,9 @@ public class TcpNioClientConnectionFactory extends
super(host, port);
}
/**
* @throws Exception
* @throws IOException
* @throws SocketException
*/
@Override
protected TcpConnectionSupport obtainConnection() throws Exception {
protected void checkActive() throws IOException {
super.checkActive();
int n = 0;
while (this.selector == null) {
try {
@@ -76,16 +71,13 @@ public class TcpNioClientConnectionFactory extends
Thread.currentThread().interrupt();
}
if (n++ > 600) {
throw new Exception("Factory failed to start");
throw new IOException("Factory failed to start");
}
}
TcpConnectionSupport theConnection = this.getTheConnection();
if (theConnection != null && theConnection.isOpen()) {
return theConnection;
}
if (logger.isDebugEnabled()) {
logger.debug("Opening new socket channel connection to " + this.getHost() + ":" + this.getPort());
}
}
@Override
protected TcpConnectionSupport buildNewConnection() throws Exception {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(this.getHost(), this.getPort()));
setSocketAttributes(socketChannel.socket());
TcpNioConnection connection = this.tcpNioConnectionSupport.createNewConnection(

View File

@@ -168,6 +168,7 @@ public class TcpNioServerConnectionFactory extends AbstractServerConnectionFacto
connection.setLastRead(now);
this.channelMap.put(channel, connection);
channel.register(selector, SelectionKey.OP_READ, connection);
connection.publishConnectionOpenEvent();
}
catch (Exception e) {
logger.error("Exception accepting new connection", e);

View File

@@ -13,9 +13,9 @@
<bean id="tcpIpUtils" class="org.springframework.integration.test.util.SocketUtils" />
<int-ip:tcp-connection-factory id="server"
<int-ip:tcp-connection-factory id="serverNet"
type="server"
using-nio="true"
using-nio="false"
single-use="true"
port="#{tcpIpUtils.findAvailableServerSocket(10000)}"
task-executor="exec"
@@ -23,18 +23,44 @@
so-timeout="20000"
/>
<int-ip:tcp-connection-factory id="client"
<int-ip:tcp-connection-factory id="clientNet"
type="client"
host="localhost"
port="#{server.port}"
port="#{serverNet.port}"
single-use="true"
lookup-host="false"
so-timeout="100000"
/>
<int-ip:tcp-inbound-gateway id="looper"
<int-ip:tcp-connection-factory id="serverNio"
type="server"
using-nio="true"
single-use="true"
port="#{tcpIpUtils.findAvailableServerSocket(20000)}"
task-executor="exec"
lookup-host="false"
so-timeout="20000"
/>
<int-ip:tcp-connection-factory id="clientNio"
type="client"
host="localhost"
using-nio="true"
port="#{serverNio.port}"
single-use="true"
lookup-host="false"
so-timeout="100000"
/>
<int-ip:tcp-inbound-gateway id="gwNet"
request-channel="serverSideChannel"
connection-factory="server"
connection-factory="serverNet"
reply-timeout="1"
/>
<int-ip:tcp-inbound-gateway id="gwNio"
request-channel="serverSideChannel"
connection-factory="serverNio"
reply-timeout="1"
/>

View File

@@ -65,10 +65,16 @@ public class ConnectionToConnectionTests {
AbstractApplicationContext ctx;
@Autowired
private AbstractClientConnectionFactory client;
private AbstractClientConnectionFactory clientNet;
@Autowired
private AbstractServerConnectionFactory server;
private AbstractServerConnectionFactory serverNet;
@Autowired
private AbstractClientConnectionFactory clientNio;
@Autowired
private AbstractServerConnectionFactory serverNio;
@Autowired
private QueueChannel serverSideChannel;
@@ -90,9 +96,19 @@ public class ConnectionToConnectionTests {
ctx.close();
}
@SuppressWarnings("unchecked")
@Test
public void testConnect() throws Exception {
public void testConnectNet() throws Exception {
testConnectGuts(this.clientNet, this.serverNet, "gwNet", true);
}
@Test
public void testConnectNio() throws Exception {
testConnectGuts(this.clientNio, this.serverNio, "gwNio", false);
}
@SuppressWarnings("unchecked")
private void testConnectGuts(AbstractClientConnectionFactory client, AbstractServerConnectionFactory server,
String gatewayName, boolean expectExceptionOnClose) throws Exception {
TestingUtilities.waitListening(server, null);
client.start();
for (int i = 0; i < 100; i++) {
@@ -102,7 +118,7 @@ public class ConnectionToConnectionTests {
assertNotNull(message);
MessageHistory history = MessageHistory.read(message);
//org.springframework.integration.test.util.TestUtils
Properties componentHistoryRecord = TestUtils.locateComponentInHistory(history, "looper", 0);
Properties componentHistoryRecord = TestUtils.locateComponentInHistory(history, gatewayName, 0);
assertNotNull(componentHistoryRecord);
assertTrue(componentHistoryRecord.get("type").equals("ip:tcp-inbound-gateway"));
assertNotNull(message);
@@ -116,7 +132,7 @@ public class ConnectionToConnectionTests {
Message<TcpConnectionEvent> eventMessage;
while ((eventMessage = (Message<TcpConnectionEvent>) events.receive(1000)) != null) {
TcpConnectionEvent event = eventMessage.getPayload();
if ("client".equals(event.getConnectionFactoryName())) {
if (event.getConnectionFactoryName().startsWith("client")) {
if (event instanceof TcpConnectionOpenEvent) {
clientOpens++;
}
@@ -127,7 +143,7 @@ public class ConnectionToConnectionTests {
clientExceptions++;
}
}
else if ("server".equals(event.getConnectionFactoryName())) {
else if (event.getConnectionFactoryName().startsWith("server")) {
if (event instanceof TcpConnectionOpenEvent) {
serverOpens++;
}
@@ -138,7 +154,9 @@ public class ConnectionToConnectionTests {
}
assertEquals(100, clientOpens);
assertEquals(100, clientCloses);
assertEquals(100, clientExceptions);
if (expectExceptionOnClose) {
assertEquals(100, clientExceptions);
}
assertEquals(100, serverOpens);
assertEquals(100, serverCloses);
}
@@ -146,16 +164,16 @@ public class ConnectionToConnectionTests {
@Test
public void testConnectRaw() throws Exception {
ByteArrayRawSerializer serializer = new ByteArrayRawSerializer();
client.setSerializer(serializer);
server.setDeserializer(serializer);
client.start();
TcpConnection connection = client.getConnection();
clientNet.setSerializer(serializer);
serverNet.setDeserializer(serializer);
clientNet.start();
TcpConnection connection = clientNet.getConnection();
connection.send(MessageBuilder.withPayload("Test").build());
Message<?> message = serverSideChannel.receive(10000);
assertNotNull(message);
MessageHistory history = MessageHistory.read(message);
//org.springframework.integration.test.util.TestUtils
Properties componentHistoryRecord = TestUtils.locateComponentInHistory(history, "looper", 0);
Properties componentHistoryRecord = TestUtils.locateComponentInHistory(history, "gwNet", 0);
assertNotNull(componentHistoryRecord);
assertTrue(componentHistoryRecord.get("type").equals("ip:tcp-inbound-gateway"));
assertNotNull(message);
@@ -164,16 +182,16 @@ public class ConnectionToConnectionTests {
@Test
public void testLookup() throws Exception {
client.start();
TcpConnection connection = client.getConnection();
clientNet.start();
TcpConnection connection = clientNet.getConnection();
assertFalse(connection.getConnectionId().contains("localhost"));
connection.close();
client.setLookupHost(true);
connection = client.getConnection();
clientNet.setLookupHost(true);
connection = clientNet.getConnection();
assertTrue(connection.getConnectionId().contains("localhost"));
connection.close();
client.setLookupHost(false);
connection = client.getConnection();
clientNet.setLookupHost(false);
connection = clientNet.getConnection();
assertFalse(connection.getConnectionId().contains("localhost"));
connection.close();
}

View File

@@ -52,10 +52,10 @@ public class ConnectionEventTests {
theEvent.add((TcpConnectionEvent) event);
}
}, "foo");
assertTrue(theEvent.size() > 0);
assertNotNull(theEvent.get(0));
assertTrue(theEvent.get(0) instanceof TcpConnectionOpenEvent);
assertTrue(theEvent.get(0).toString().endsWith("[factory=foo, connectionId=" + conn.getConnectionId() + "] **OPENED**"));
/*
* Open is not published by the connection itself; the factory publishes it after initialization.
* See ConnectionToConnectionTests.
*/
@SuppressWarnings("unchecked")
Serializer<Object> serializer = mock(Serializer.class);
RuntimeException toBeThrown = new RuntimeException("foo");
@@ -67,17 +67,17 @@ public class ConnectionEventTests {
fail("Expected exception");
}
catch (Exception e) {}
assertTrue(theEvent.size() > 1);
assertNotNull(theEvent.get(1));
assertTrue(theEvent.get(1) instanceof TcpConnectionExceptionEvent);
assertTrue(theEvent.get(1).toString().endsWith("[factory=foo, connectionId=" + conn.getConnectionId() + "]"));
assertTrue(theEvent.get(1).toString().contains("cause=java.lang.RuntimeException: foo]"));
TcpConnectionExceptionEvent event = (TcpConnectionExceptionEvent) theEvent.get(1);
assertTrue(theEvent.size() > 0);
assertNotNull(theEvent.get(0));
assertTrue(theEvent.get(0) instanceof TcpConnectionExceptionEvent);
assertTrue(theEvent.get(0).toString().endsWith("[factory=foo, connectionId=" + conn.getConnectionId() + "]"));
assertTrue(theEvent.get(0).toString().contains("cause=java.lang.RuntimeException: foo]"));
TcpConnectionExceptionEvent event = (TcpConnectionExceptionEvent) theEvent.get(0);
assertNotNull(event.getCause());
assertSame(toBeThrown, event.getCause());
assertTrue(theEvent.size() > 2);
assertNotNull(theEvent.get(2));
assertTrue(theEvent.get(2).toString().endsWith("[factory=foo, connectionId=" + conn.getConnectionId() + "] **CLOSED**"));
assertTrue(theEvent.size() > 1);
assertNotNull(theEvent.get(1));
assertTrue(theEvent.get(1).toString().endsWith("[factory=foo, connectionId=" + conn.getConnectionId() + "] **CLOSED**"));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -107,9 +107,7 @@ public class JdbcOutboundGateway extends AbstractReplyProducingMessageHandler im
}
@Override
protected void onInit() {
super.onInit();
protected void doInit() {
if (this.maxRowsPerPoll != null) {
Assert.notNull(poller, "If you want to set 'maxRowsPerPoll', then you must provide a 'selectQuery'.");
poller.setMaxRowsPerPoll(this.maxRowsPerPoll);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -88,8 +88,7 @@ public class StoredProcOutboundGateway extends AbstractReplyProducingMessageHand
* when {@link ProcedureParameter} are passed in.
*/
@Override
protected void onInit() {
super.onInit();
protected void doInit() {
};
@Override

View File

@@ -31,6 +31,7 @@ import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.serializer.Deserializer;
@@ -40,12 +41,12 @@ import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.integration.jdbc.JdbcMessageStore;
import org.springframework.integration.jdbc.store.channel.ChannelMessageStoreQueryProvider;
import org.springframework.integration.jdbc.store.channel.DerbyChannelMessageStoreQueryProvider;
import org.springframework.integration.jdbc.store.channel.MessageRowMapper;
import org.springframework.integration.jdbc.store.channel.MySqlChannelMessageStoreQueryProvider;
import org.springframework.integration.jdbc.store.channel.OracleChannelMessageStoreQueryProvider;
import org.springframework.integration.jdbc.store.channel.PostgresChannelMessageStoreQueryProvider;
import org.springframework.integration.jdbc.store.channel.ChannelMessageStoreQueryProvider;
import org.springframework.integration.store.AbstractMessageGroupStore;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.MessageGroupStore;
@@ -590,7 +591,9 @@ public class JdbcChannelMessageStore extends AbstractMessageGroupStore implement
final Message<?> polledMessage = this.doPollForMessage(key);
if (polledMessage != null){
this.removeMessageFromGroup(groupId, polledMessage);
if (!this.doRemoveMessageFromGroup(groupId, polledMessage)) {
return null;
}
}
return polledMessage;
@@ -605,18 +608,26 @@ public class JdbcChannelMessageStore extends AbstractMessageGroupStore implement
*/
public MessageGroup removeMessageFromGroup(Object groupId, Message<?> messageToRemove) {
this.doRemoveMessageFromGroup(groupId, messageToRemove);
return getMessageGroup(groupId);
}
private boolean doRemoveMessageFromGroup(Object groupId, Message<?> messageToRemove) {
final UUID id = messageToRemove.getHeaders().getId();
int updated = jdbcTemplate.update(getQuery(channelMessageStoreQueryProvider.getDeleteMessageQuery()), new Object[] { getKey(id), getKey(groupId), region }, new int[] {
Types.VARCHAR, Types.VARCHAR, Types.VARCHAR });
if (updated != 0) {
boolean result = updated != 0;
if (result) {
logger.debug(String.format("Message with id '%s' was deleted.", id));
} else {
}
else {
logger.warn(String.format("Message with id '%s' was not deleted.", id));
}
return getMessageGroup(groupId);
return result;
}
/**

View File

@@ -12,15 +12,14 @@
*/
package org.springframework.integration.jdbc;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import javax.sql.DataSource;
import org.junit.Assert;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
@@ -42,7 +41,7 @@ public class JdbcOutboundGatewayTests {
try {
jdbcOutboundGateway.setMaxRowsPerPoll(10);
jdbcOutboundGateway.onInit();
jdbcOutboundGateway.afterPropertiesSet();
} catch (IllegalArgumentException e) {
assertEquals("If you want to set 'maxRowsPerPoll', then you must provide a 'selectQuery'.", e.getMessage());

View File

@@ -12,27 +12,32 @@
*/
package org.springframework.integration.jdbc.store.channel;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Assert;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Assert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.integration.jdbc.store.JdbcChannelMessageStore;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.jdbc.store.JdbcChannelMessageStore;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
@@ -62,8 +67,19 @@ abstract class AbstractTxTimeoutMessageStoreTests {
protected TestService testService;
@Autowired
@Qualifier("store")
protected JdbcChannelMessageStore jdbcChannelMessageStore;
@Autowired
private MessageChannel first;
@Autowired
private CountDownLatch successfulLatch;
@Autowired
private AtomicInteger errorAtomicInteger;
public void test() throws InterruptedException {
int maxMessages = 10;
@@ -141,7 +157,9 @@ abstract class AbstractTxTimeoutMessageStoreTests {
return true;
}
});
if (!result) return false;
if (!result) {
return false;
}
}
return true;
@@ -157,4 +175,14 @@ abstract class AbstractTxTimeoutMessageStoreTests {
assertTrue(executorService.awaitTermination(5, TimeUnit.SECONDS));
}
public void testInt3181ConcurrentPolling() throws InterruptedException {
for (int i = 0; i < 10; i++) {
this.first.send(new GenericMessage<Object>("test"));
}
assertTrue(this.successfulLatch.await(5, TimeUnit.SECONDS));
assertEquals(0, errorAtomicInteger.get());
}
}

View File

@@ -15,6 +15,7 @@ package org.springframework.integration.jdbc.store.channel;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -36,4 +37,10 @@ public class DerbyTxTimeoutMessageStoreTests extends AbstractTxTimeoutMessageSto
super.test();
}
@Test
@Override
public void testInt3181ConcurrentPolling() throws InterruptedException {
super.testInt3181ConcurrentPolling();
}
}

View File

@@ -16,6 +16,7 @@ import java.util.concurrent.ExecutionException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -40,4 +41,10 @@ public class HsqlTxTimeoutMessageStoreTests extends AbstractTxTimeoutMessageStor
super.testInt2993IdCacheConcurrency();
}
@Test
@Override
public void testInt3181ConcurrentPolling() throws InterruptedException {
super.testInt3181ConcurrentPolling();
}
}

View File

@@ -15,6 +15,7 @@ package org.springframework.integration.jdbc.store.channel;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -35,4 +36,10 @@ public class MySqlTxTimeoutMessageStoreTests extends AbstractTxTimeoutMessageSto
super.test();
}
@Test
@Override
public void testInt3181ConcurrentPolling() throws InterruptedException {
super.testInt3181ConcurrentPolling();
}
}

View File

@@ -54,5 +54,44 @@
<int:logging-channel-adapter id="loggit" log-full-message="true" level="ERROR"/>
<task:executor id="threadPoolTaskExecutor" pool-size="100"/>
<bean id="messageStore" class="org.springframework.integration.jdbc.store.JdbcChannelMessageStore">
<property name="dataSource" ref="dataSource"/>
<property name="region" value="CONCURRENT_POLL"/>
<property name="channelMessageStoreQueryProvider" ref="queryProvider"/>
</bean>
<int:channel id="first">
<int:queue message-store="messageStore"/>
</int:channel>
<int:channel id="second">
<int:queue message-store="messageStore"/>
</int:channel>
<int:bridge input-channel="first" output-channel="second">
<int:poller fixed-rate="200" task-executor="threadPoolTaskExecutor" max-messages-per-poll="3">
<int:transactional transaction-manager="transactionManager"/>
</int:poller>
</int:bridge>
<bean id="successfulLatch" class="java.util.concurrent.CountDownLatch">
<constructor-arg value="10"/>
</bean>
<int:outbound-channel-adapter channel="second" expression="@successfulLatch.countDown()">
<int:poller fixed-delay="1000">
<int:transactional transaction-manager="transactionManager"/>
</int:poller>
</int:outbound-channel-adapter>
<bean id="errorAtomicInteger" class="java.util.concurrent.atomic.AtomicInteger"/>
<int:service-activator input-channel="errorChannel" output-channel="nullChannel"
expression="@errorAtomicInteger.incrementAndGet()">
<int:poller fixed-delay="1000"/>
</int:service-activator>
</beans>

View File

@@ -460,7 +460,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
}
@Override
public final void onInit() {
protected void doInit() {
synchronized (this.initializationMonitor) {
if (this.initialized) {
return;
@@ -470,7 +470,6 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp
^ this.requestDestinationName != null
^ this.requestDestinationExpressionProcessor != null,
"Exactly one of 'requestDestination', 'requestDestinationName', or 'requestDestinationExpression' is required.");
super.onInit();
if (this.requestDestinationExpressionProcessor != null) {
this.requestDestinationExpressionProcessor.setBeanFactory(getBeanFactory());
this.requestDestinationExpressionProcessor.setConversionService(getConversionService());

View File

@@ -62,172 +62,171 @@ import org.springframework.util.ObjectUtils;
*/
public class OperationInvokingMessageHandler extends AbstractReplyProducingMessageHandler implements InitializingBean {
private volatile MBeanServerConnection server;
private volatile MBeanServerConnection server;
private volatile ObjectName objectName;
private volatile ObjectName objectName;
private volatile String operationName;
private volatile String operationName;
/**
* Provide a reference to the MBeanServer within which the MBean
* target for operation invocation has been registered.
*/
public void setServer(MBeanServerConnection server) {
this.server = server;
}
/**
* Provide a reference to the MBeanServer within which the MBean
* target for operation invocation has been registered.
*/
public void setServer(MBeanServerConnection server) {
this.server = server;
}
/**
* Specify a default ObjectName to use when no such header is
* available on the Message being handled.
*/
public void setObjectName(String objectName) {
try {
if (objectName != null) {
this.objectName = ObjectNameManager.getInstance(objectName);
}
}
catch (MalformedObjectNameException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Specify a default ObjectName to use when no such header is
* available on the Message being handled.
*/
public void setObjectName(String objectName) {
try {
if (objectName != null) {
this.objectName = ObjectNameManager.getInstance(objectName);
}
}
catch (MalformedObjectNameException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Specify an operation name to be invoked when no such
* header is available on the Message being handled.
*/
public void setOperationName(String operationName) {
this.operationName = operationName;
}
/**
* Specify an operation name to be invoked when no such
* header is available on the Message being handled.
*/
public void setOperationName(String operationName) {
this.operationName = operationName;
}
@Override
public final void onInit() {
Assert.notNull(this.server, "MBeanServer is required.");
super.onInit();
}
@Override
protected void doInit() {
Assert.notNull(this.server, "MBeanServer is required.");
}
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
ObjectName objectName = this.resolveObjectName(requestMessage);
String operationName = this.resolveOperationName(requestMessage);
Map<String, Object> paramsFromMessage = this.resolveParameters(requestMessage);
try {
MBeanInfo mbeanInfo = this.server.getMBeanInfo(objectName);
MBeanOperationInfo[] opInfoArray = mbeanInfo.getOperations();
boolean hasNoArgOption = false;
for (MBeanOperationInfo opInfo : opInfoArray) {
if (operationName.equals(opInfo.getName())) {
MBeanParameterInfo[] paramInfoArray = opInfo.getSignature();
if (paramInfoArray.length == 0) {
hasNoArgOption = true;
}
if (paramInfoArray.length == paramsFromMessage.size()) {
int index = 0;
Object values[] = new Object[paramInfoArray.length];
String signature[] = new String[paramInfoArray.length];
for (MBeanParameterInfo paramInfo : paramInfoArray) {
Object value = paramsFromMessage.get(paramInfo.getName());
if (value == null) {
/*
* With Spring 3.2.3 and greater, the parameter names are
* registered instead of the JVM's default p1, p2 etc.
* Fall back to that naming style if not found.
*/
value = paramsFromMessage.get("p" + (index + 1));
}
if (value != null && value.getClass().getName().equals(paramInfo.getType())) {
values[index] = value;
signature[index] = paramInfo.getType();
index++;
}
}
if (index == paramInfoArray.length) {
return this.server.invoke(objectName, operationName, values, signature);
}
}
}
}
if (hasNoArgOption) {
return this.server.invoke(objectName, operationName, null, null);
}
throw new MessagingException(requestMessage, "failed to find JMX operation '"
+ operationName + "' on MBean [" + objectName + "] of type [" + mbeanInfo.getClassName()
+ "] with " + paramsFromMessage.size() + " parameters: " + paramsFromMessage.keySet());
}
catch (JMException e) {
throw new MessageHandlingException(requestMessage, "failed to invoke JMX operation '" +
operationName + "' on MBean [" + objectName + "]" + " with " +
paramsFromMessage.size() + " parameters: " + paramsFromMessage.keySet(), e);
}
catch (IOException e) {
throw new MessageHandlingException(requestMessage, "IOException on MBeanServerConnection", e);
}
}
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
ObjectName objectName = this.resolveObjectName(requestMessage);
String operationName = this.resolveOperationName(requestMessage);
Map<String, Object> paramsFromMessage = this.resolveParameters(requestMessage);
try {
MBeanInfo mbeanInfo = this.server.getMBeanInfo(objectName);
MBeanOperationInfo[] opInfoArray = mbeanInfo.getOperations();
boolean hasNoArgOption = false;
for (MBeanOperationInfo opInfo : opInfoArray) {
if (operationName.equals(opInfo.getName())) {
MBeanParameterInfo[] paramInfoArray = opInfo.getSignature();
if (paramInfoArray.length == 0) {
hasNoArgOption = true;
}
if (paramInfoArray.length == paramsFromMessage.size()) {
int index = 0;
Object values[] = new Object[paramInfoArray.length];
String signature[] = new String[paramInfoArray.length];
for (MBeanParameterInfo paramInfo : paramInfoArray) {
Object value = paramsFromMessage.get(paramInfo.getName());
if (value == null) {
/*
* With Spring 3.2.3 and greater, the parameter names are
* registered instead of the JVM's default p1, p2 etc.
* Fall back to that naming style if not found.
*/
value = paramsFromMessage.get("p" + (index + 1));
}
if (value != null && value.getClass().getName().equals(paramInfo.getType())) {
values[index] = value;
signature[index] = paramInfo.getType();
index++;
}
}
if (index == paramInfoArray.length) {
return this.server.invoke(objectName, operationName, values, signature);
}
}
}
}
if (hasNoArgOption) {
return this.server.invoke(objectName, operationName, null, null);
}
throw new MessagingException(requestMessage, "failed to find JMX operation '"
+ operationName + "' on MBean [" + objectName + "] of type [" + mbeanInfo.getClassName()
+ "] with " + paramsFromMessage.size() + " parameters: " + paramsFromMessage.keySet());
}
catch (JMException e) {
throw new MessageHandlingException(requestMessage, "failed to invoke JMX operation '" +
operationName + "' on MBean [" + objectName + "]" + " with " +
paramsFromMessage.size() + " parameters: " + paramsFromMessage.keySet(), e);
}
catch (IOException e) {
throw new MessageHandlingException(requestMessage, "IOException on MBeanServerConnection", e);
}
}
/**
* First checks if defaultObjectName is set, otherwise falls back on {@link JmxHeaders#OBJECT_NAME} header.
*/
private ObjectName resolveObjectName(Message<?> message) {
ObjectName objectName = this.objectName;
if (objectName == null){
Object objectNameHeader = message.getHeaders().get(JmxHeaders.OBJECT_NAME);
if (objectNameHeader instanceof ObjectName) {
objectName = (ObjectName) objectNameHeader;
}
else if (objectNameHeader instanceof String) {
try {
objectName = ObjectNameManager.getInstance(objectNameHeader);
}
catch (MalformedObjectNameException e) {
throw new IllegalArgumentException(e);
}
}
}
Assert.notNull(objectName, "Failed to resolve ObjectName.");
return objectName;
}
/**
* First checks if defaultObjectName is set, otherwise falls back on {@link JmxHeaders#OBJECT_NAME} header.
*/
private ObjectName resolveObjectName(Message<?> message) {
ObjectName objectName = this.objectName;
if (objectName == null){
Object objectNameHeader = message.getHeaders().get(JmxHeaders.OBJECT_NAME);
if (objectNameHeader instanceof ObjectName) {
objectName = (ObjectName) objectNameHeader;
}
else if (objectNameHeader instanceof String) {
try {
objectName = ObjectNameManager.getInstance(objectNameHeader);
}
catch (MalformedObjectNameException e) {
throw new IllegalArgumentException(e);
}
}
}
Assert.notNull(objectName, "Failed to resolve ObjectName.");
return objectName;
}
/**
* First checks if defaultOperationName is set, otherwise falls back on {@link JmxHeaders#OPERATION_NAME} header.
*/
private String resolveOperationName(Message<?> message) {
String operationName = this.operationName;
if (operationName == null){
operationName = message.getHeaders().get(JmxHeaders.OPERATION_NAME, String.class);
}
Assert.notNull(operationName, "Failed to resolve operation name.");
return operationName;
}
/**
* First checks if defaultOperationName is set, otherwise falls back on {@link JmxHeaders#OPERATION_NAME} header.
*/
private String resolveOperationName(Message<?> message) {
String operationName = this.operationName;
if (operationName == null){
operationName = message.getHeaders().get(JmxHeaders.OPERATION_NAME, String.class);
}
Assert.notNull(operationName, "Failed to resolve operation name.");
return operationName;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Map<String, Object> resolveParameters(Message<?> message) {
Map<String, Object> map = null;
if (message.getPayload() instanceof Map) {
map = (Map<String, Object>) message.getPayload();
}
else if (message.getPayload() instanceof List) {
map = this.createParameterMapFromList((List) message.getPayload());
}
else if (message.getPayload() != null && message.getPayload().getClass().isArray()) {
map = this.createParameterMapFromList(
Arrays.asList(ObjectUtils.toObjectArray(message.getPayload())));
}
else if (message.getPayload() != null) {
map = this.createParameterMapFromList(Collections.singletonList(message.getPayload()));
}
else {
map = Collections.EMPTY_MAP;
}
return map;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Map<String, Object> resolveParameters(Message<?> message) {
Map<String, Object> map = null;
if (message.getPayload() instanceof Map) {
map = (Map<String, Object>) message.getPayload();
}
else if (message.getPayload() instanceof List) {
map = this.createParameterMapFromList((List) message.getPayload());
}
else if (message.getPayload() != null && message.getPayload().getClass().isArray()) {
map = this.createParameterMapFromList(
Arrays.asList(ObjectUtils.toObjectArray(message.getPayload())));
}
else if (message.getPayload() != null) {
map = this.createParameterMapFromList(Collections.singletonList(message.getPayload()));
}
else {
map = Collections.EMPTY_MAP;
}
return map;
}
@SuppressWarnings("rawtypes")
private Map<String, Object> createParameterMapFromList(List parameters) {
Map<String, Object> map = new HashMap<String, Object>();
for (int i = 0; i < parameters.size(); i++) {
map.put("p" + (i + 1), parameters.get(i));
}
return map;
}
@SuppressWarnings("rawtypes")
private Map<String, Object> createParameterMapFromList(List parameters) {
Map<String, Object> map = new HashMap<String, Object>();
for (int i = 0; i < parameters.size(); i++) {
map.put("p" + (i + 1), parameters.get(i));
}
return map;
}
}

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:int-jmx="http://www.springframework.org/schema/integration/jmx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/jmx http://www.springframework.org/schema/integration/jmx/spring-integration-jmx.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:mbean-server/>
<int-jmx:mbean-export/>
<message-history/>
<service-activator id="gatewayTestService" input-channel="gatewayTestInputChannel" ref="gateway"/>
<service-activator id="replyingHandlerTestService" input-channel="replyingHandlerTestInputChannel">
<beans:bean
class="org.springframework.integration.jmx.ServiceActivatorDefaultFrameworkMethodTests$TestReplyingMessageHandler"/>
</service-activator>
<service-activator id="optimizedRefReplyingHandlerTestService"
input-channel="optimizedRefReplyingHandlerTestInputChannel" ref="testReplyingMessageHandler"/>
<service-activator id="replyingHandlerWithStandardMethodTestService"
input-channel="replyingHandlerWithStandardMethodTestInputChannel"
method="handleMessage">
<beans:bean
class="org.springframework.integration.jmx.ServiceActivatorDefaultFrameworkMethodTests$TestReplyingMessageHandler"/>
</service-activator>
<service-activator id="replyingHandlerWithOtherMethodTestService"
input-channel="replyingHandlerWithOtherMethodTestInputChannel"
method="foo">
<beans:bean
class="org.springframework.integration.jmx.ServiceActivatorDefaultFrameworkMethodTests$TestReplyingMessageHandler"/>
</service-activator>
<service-activator id="handlerTestService" input-channel="handlerTestInputChannel">
<beans:bean
class="org.springframework.integration.jmx.ServiceActivatorDefaultFrameworkMethodTests$TestMessageHandler"/>
</service-activator>
<service-activator id="processorTestService" input-channel="processorTestInputChannel" ref="testMessageProcessor"/>
<gateway id="gateway" default-request-channel="requestChannel" default-reply-channel="replyChannel"/>
<channel id="requestChannel"/>
<bridge id="bridge" input-channel="requestChannel" output-channel="replyChannel"/>
<channel id="replyChannel">
<queue/>
</channel>
<beans:bean id="testReplyingMessageHandler"
class="org.springframework.integration.jmx.ServiceActivatorDefaultFrameworkMethodTests$TestReplyingMessageHandler"/>
<beans:bean id="testMessageProcessor" class="org.springframework.integration.jmx.ServiceActivatorDefaultFrameworkMethodTests$TestMessageProcessor">
<beans:property name="prefix" value="foo"/>
</beans:bean>
</beans:beans>

View File

@@ -0,0 +1,202 @@
/*
* Copyright 2002-2013 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
*
* http://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.jmx;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* See INT-1688 for background.
*
* @author Mark Fisher
* @author Artem Bilan
* @author Gary Russell
* @since 2.0.1
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class ServiceActivatorDefaultFrameworkMethodTests {
@Autowired
private MessageChannel gatewayTestInputChannel;
@Autowired
private MessageChannel replyingHandlerTestInputChannel;
@Autowired
private MessageChannel optimizedRefReplyingHandlerTestInputChannel;
@Autowired
private MessageChannel replyingHandlerWithStandardMethodTestInputChannel;
@Autowired
private MessageChannel replyingHandlerWithOtherMethodTestInputChannel;
@Autowired
private MessageChannel handlerTestInputChannel;
@Autowired
private MessageChannel processorTestInputChannel;
@Autowired
private EventDrivenConsumer processorTestService;
@Autowired
private MessageProcessor<?> testMessageProcessor;
@Test
public void testGateway() {
QueueChannel replyChannel = new QueueChannel();
Message<?> message = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build();
this.gatewayTestInputChannel.send(message);
Message<?> reply = replyChannel.receive(0);
assertEquals("gatewayTestInputChannel,gatewayTestService,gateway,requestChannel,bridge,replyChannel", reply.getHeaders().get("history").toString());
}
@Test
public void testReplyingMessageHandler() {
QueueChannel replyChannel = new QueueChannel();
Message<?> message = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build();
this.replyingHandlerTestInputChannel.send(message);
Message<?> reply = replyChannel.receive(0);
assertEquals("TEST", reply.getPayload());
assertEquals("replyingHandlerTestInputChannel,replyingHandlerTestService", reply.getHeaders().get("history").toString());
StackTraceElement[] st = (StackTraceElement[]) reply.getHeaders().get("callStack");
assertEquals("doDispatch", st[15].getMethodName()); // close to the metal
}
@Test
public void testOptimizedReplyingMessageHandler() {
QueueChannel replyChannel = new QueueChannel();
Message<?> message = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build();
this.optimizedRefReplyingHandlerTestInputChannel.send(message);
Message<?> reply = replyChannel.receive(0);
assertEquals("TEST", reply.getPayload());
assertEquals("optimizedRefReplyingHandlerTestInputChannel,optimizedRefReplyingHandlerTestService",
reply.getHeaders().get("history").toString());
StackTraceElement[] st = (StackTraceElement[]) reply.getHeaders().get("callStack");
assertEquals("doDispatch", st[15].getMethodName());
}
@Test
public void testReplyingMessageHandlerWithStandardMethod() {
QueueChannel replyChannel = new QueueChannel();
Message<?> message = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build();
this.replyingHandlerWithStandardMethodTestInputChannel.send(message);
Message<?> reply = replyChannel.receive(0);
assertEquals("TEST", reply.getPayload());
assertEquals("replyingHandlerWithStandardMethodTestInputChannel,replyingHandlerWithStandardMethodTestService", reply.getHeaders().get("history").toString());
StackTraceElement[] st = (StackTraceElement[]) reply.getHeaders().get("callStack");
assertEquals("doDispatch", st[15].getMethodName()); // close to the metal
}
@Test
public void testReplyingMessageHandlerWithOtherMethod() {
QueueChannel replyChannel = new QueueChannel();
Message<?> message = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build();
this.replyingHandlerWithOtherMethodTestInputChannel.send(message);
Message<?> reply = replyChannel.receive(0);
assertEquals("bar", reply.getPayload());
assertEquals("replyingHandlerWithOtherMethodTestInputChannel,replyingHandlerWithOtherMethodTestService", reply.getHeaders().get("history").toString());
}
@Test
public void testMessageHandler() {
QueueChannel replyChannel = new QueueChannel();
Message<?> message = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build();
this.handlerTestInputChannel.send(message);
}
// INT-2399
@Test
public void testMessageProcessor() {
Object processor = TestUtils.getPropertyValue(processorTestService, "handler.h.advised.targetSource.target.processor");
assertSame(testMessageProcessor, processor);
QueueChannel replyChannel = new QueueChannel();
Message<?> message = MessageBuilder.withPayload("bar").setReplyChannel(replyChannel).build();
this.processorTestInputChannel.send(message);
Message<?> reply = replyChannel.receive(0);
assertEquals("foo:bar", reply.getPayload());
assertEquals("processorTestInputChannel,processorTestService", reply.getHeaders().get("history").toString());
}
private interface Foo {
public String foo(String in);
}
@SuppressWarnings("unused")
private static class TestReplyingMessageHandler extends AbstractReplyProducingMessageHandler implements Foo {
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
Exception e = new RuntimeException();
StackTraceElement[] st = e.getStackTrace();
return MessageBuilder.withPayload(requestMessage.getPayload().toString().toUpperCase())
.setHeader("callStack", st);
}
public String foo(String in) {
return "bar";
}
}
@SuppressWarnings("unused")
private static class TestMessageHandler implements MessageHandler {
@Override
public void handleMessage(Message<?> requestMessage) {
Exception e = new RuntimeException();
StackTraceElement[] st = e.getStackTrace();
assertEquals("doDispatch", st[28].getMethodName());
}
}
@SuppressWarnings("unused")
private static class TestMessageProcessor implements MessageProcessor<String> {
private String prefix;
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String processMessage(Message<?> message) {
return prefix + ":" + message.getPayload();
}
}
}

View File

@@ -62,12 +62,8 @@ public class JpaOutboundGateway extends AbstractReplyProducingMessageHandler {
}
/**
*
*/
@Override
protected void onInit() {
super.onInit();
protected void doInit() {
this.jpaExecutor.setBeanFactory(this.getBeanFactory());
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2002-2013 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
*
*
* http://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.
@@ -27,7 +27,7 @@ import org.springframework.util.Assert;
/**
* Base {@link MessageProcessor} for scripting implementations to extend.
*
*
* @author Mark Fisher
* @author Stefan Reuter
* @since 2.0
@@ -36,9 +36,9 @@ public abstract class AbstractScriptExecutingMessageProcessor<T> implements Mess
private final ScriptVariableGenerator scriptVariableGenerator;
private volatile ClassLoader beanClassLoader;
protected volatile ClassLoader beanClassLoader;
private volatile BeanFactory beanFactory;
protected volatile BeanFactory beanFactory;
protected AbstractScriptExecutingMessageProcessor() {
this.scriptVariableGenerator = new DefaultScriptVariableGenerator();
@@ -48,7 +48,7 @@ public abstract class AbstractScriptExecutingMessageProcessor<T> implements Mess
Assert.notNull(scriptVariableGenerator, "scriptVariableGenerator must not be null");
this.scriptVariableGenerator = scriptVariableGenerator;
}
/**
* Executes the script and returns the result.
@@ -69,23 +69,15 @@ public abstract class AbstractScriptExecutingMessageProcessor<T> implements Mess
this.beanClassLoader = classLoader;
}
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
protected BeanFactory getBeanFactory() {
return this.beanFactory;
}
/**
* Subclasses must implement this method to create a script source,
* optionally using the message to locate or create the script.
*
*
* @param message the message being processed
* @return a ScriptSource to use to create a script
*/

View File

@@ -72,7 +72,6 @@ public class Jsr223ServiceActivatorTests {
String value1 = (String) replyChannel.receive(0).getPayload();
String value2 = (String) replyChannel.receive(0).getPayload();
String value3 = (String) replyChannel.receive(0).getPayload();
System.out.println(value1 + "\n" + value2 + "\n" + value3);
assertTrue(value1.startsWith("python-test-1-foo - bar"));
assertTrue(value2.startsWith("python-test-2-foo - bar"));
assertTrue(value3.startsWith("python-test-3-foo - bar"));

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2002-2011 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
*
*
* http://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.
@@ -13,12 +13,12 @@
package org.springframework.integration.scripting.jsr223;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.integration.scripting.ScriptExecutor;
import org.springframework.integration.scripting.ScriptingException;
@@ -30,28 +30,29 @@ import org.springframework.scripting.support.StaticScriptSource;
*
*/
public class Jsr223ScriptExecutorTests {
@Test
public void test(){
public void test() {
ScriptExecutor executor = ScriptExecutorFactory.getScriptExecutor("jruby");
executor.executeScript(new StaticScriptSource("puts 'hello, world'"));
executor.executeScript(new StaticScriptSource("puts 'hello, again'"));
executor.executeScript(new StaticScriptSource("'hello, world'"));
executor.executeScript(new StaticScriptSource("'hello, again'"));
Map<String,Object> variables = new HashMap<String,Object>();
Map<String,Object> headers = new HashMap<String,Object>();
headers.put("one",1);
headers.put("two","two");
headers.put("three", new Integer(3));
headers.put("one", 1);
headers.put("two", "two");
headers.put("three", 3);
variables.put("payload", "payload");
variables.put("headers", headers);
String result = (String)executor.executeScript(
new ResourceScriptSource(new ClassPathResource("/org/springframework/integration/scripting/jsr223/print_message.rb")),
variables
);
assertEquals("payload modified",result.substring(0,"payload modified".length()));
assertEquals("payload modified", result.substring(0, "payload modified".length()));
}
@Test
public void testJs(){
@@ -59,25 +60,20 @@ public class Jsr223ScriptExecutorTests {
Object obj = executor.executeScript(new StaticScriptSource("function js(){ return 'js';} js();"));
assertEquals("js",obj.toString());
}
@Test
@Test
public void testPython() {
ScriptExecutor executor = ScriptExecutorFactory.getScriptExecutor("python");
Object obj = executor.executeScript(new StaticScriptSource("x=2") );
assertEquals(2,obj);
obj = executor.executeScript(new StaticScriptSource("def foo(y):\n\tx=y\n\treturn y\nz=foo(2)") );
assertEquals(2,obj);
}
@Test
@Test(expected = ScriptingException.class)
public void testInvalidLanguageThrowsScriptingException() {
try {
ScriptExecutor executor = ScriptExecutorFactory.getScriptExecutor("foo");
executor.executeScript(new StaticScriptSource("x=2"));
fail("should throw Exception");
} catch (ScriptingException e) {
System.out.println(e.getMessage());
}
ScriptExecutor executor = ScriptExecutorFactory.getScriptExecutor("foo");
executor.executeScript(new StaticScriptSource("x=2"));
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Copyright 2002-2013 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
*
*
* http://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.
@@ -36,39 +36,38 @@ public class PythonScriptExecutorTests {
public void init() {
executor = new PythonScriptExecutor();
}
@Test
@Test
public void testLiteral() {
Object obj = executor.executeScript(new StaticScriptSource("3+4") );
assertEquals(7,obj);
obj = executor.executeScript(new StaticScriptSource("'hello,world'") );
assertEquals("hello,world",obj);
}
@Test
@Test
public void test1() {
Object obj = executor.executeScript(new StaticScriptSource("x=2") );
assertEquals(2,obj);
}
@Test
@Test
public void test2() {
Object obj = executor.executeScript(new StaticScriptSource("def foo(y):\n\tx=y\n\treturn y\nz=foo(2)") );
assertEquals(2,obj);
}
@Test
@Test
public void test3() {
ScriptSource source =
ScriptSource source =
new ResourceScriptSource(new ClassPathResource("/org/springframework/integration/scripting/jsr223/test3.py"));
Object obj = executor.executeScript(source);
System.out.println(obj);
PyTuple tuple = (PyTuple) obj;
assertEquals(1, tuple.get(0));
}
@Test
public void testEmbeddedVariable() {
Map<String,Object> variables = new HashMap<String,Object>();

View File

@@ -46,4 +46,9 @@ public class SftpInboundFileSynchronizer extends AbstractInboundFileSynchronizer
return (file != null ? file.getFilename() : null);
}
@Override
protected long getModified(LsEntry file) {
return (long) file.getAttrs().getMTime() * 1000;
}
}

View File

@@ -236,7 +236,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="delete-remote-files" type="xsd:string">
<xsd:attribute name="delete-remote-files" type="xsd:string" default="false">
<xsd:annotation>
<xsd:documentation>
Specify whether to delete the remote source
@@ -246,6 +246,16 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="preserve-timestamp" type="xsd:string" default="false">
<xsd:annotation>
<xsd:documentation>
Specify whether to preserve the modified timestamp from the remote source
file on the local file after copying.
By default, the remote timestamp will NOT be
preserved.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

View File

@@ -22,11 +22,11 @@
<channel id="requestChannel">
<queue/>
</channel>
<beans:bean id="pattern" class="java.util.regex.Pattern" factory-method="compile">
<beans:constructor-arg value="."/>
</beans:bean>
<beans:bean id="sftpSessionFactory" class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
<beans:property name="host" value="loclahost"/>
<beans:property name="knownHosts" value="local, foo.com, bar.foo"/>
@@ -49,14 +49,15 @@
temporary-file-suffix=".bar"
comparator="comparator"
local-filter="acceptAllFilter"
delete-remote-files="${delete.remote.files}">
delete-remote-files="${delete.remote.files}"
preserve-timestamp="true">
<poller fixed-rate="1000">
<transactional synchronization-factory="syncFactory"/>
</poller>
</sftp:inbound-channel-adapter>
<beans:bean id="acceptAllFilter" class="org.springframework.integration.file.filters.AcceptAllFileListFilter" />
<transaction-synchronization-factory id="syncFactory">
<after-commit expression="'foo'" channel="successChannel"/>
<after-rollback expression="'bar'" channel="failureChannel"/>
@@ -118,7 +119,7 @@
</sftp:inbound-channel-adapter>
<bridge input-channel="autoChannel" output-channel="nullChannel" />
<beans:bean id="transactionManager" class="org.springframework.integration.transaction.PseudoTransactionManager"/>
</beans:beans>

View File

@@ -82,6 +82,7 @@ public class InboundChannelAdapterParserTests {
assertNotNull(comparator);
SftpInboundFileSynchronizer synchronizer = (SftpInboundFileSynchronizer) TestUtils.getPropertyValue(source, "synchronizer");
assertNotNull(TestUtils.getPropertyValue(synchronizer, "localFilenameGeneratorExpression"));
assertTrue(TestUtils.getPropertyValue(synchronizer, "preserveTimestamp", Boolean.class));
String remoteFileSeparator = (String) TestUtils.getPropertyValue(synchronizer, "remoteFileSeparator");
assertEquals(".bar", TestUtils.getPropertyValue(synchronizer, "temporaryFileSuffix", String.class));
assertNotNull(remoteFileSeparator);

View File

@@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -29,8 +30,10 @@ import static org.mockito.Mockito.when;
import java.io.File;
import java.io.FileInputStream;
import java.util.Calendar;
import java.util.Vector;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Test;
@@ -49,6 +52,7 @@ import com.jcraft.jsch.SftpATTRS;
* @author Oleg Zhurakousky
* @author Gunnar Hillert
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public class SftpInboundRemoteFileSystemSynchronizerTests {
@@ -81,6 +85,7 @@ public class SftpInboundRemoteFileSystemSynchronizerTests {
SftpInboundFileSynchronizer synchronizer = spy(new SftpInboundFileSynchronizer(ftpSessionFactory));
synchronizer.setDeleteRemoteFiles(true);
synchronizer.setPreserveTimestamp(true);
synchronizer.setRemoteDirectory("remote-test-dir");
synchronizer.setFilter(new SftpRegexPatternFileListFilter(".*\\.test$"));
synchronizer.setIntegrationEvaluationContext(ExpressionUtils.createStandardEvaluationContext());
@@ -93,9 +98,15 @@ public class SftpInboundRemoteFileSystemSynchronizerTests {
Message<File> atestFile = ms.receive();
assertNotNull(atestFile);
assertEquals("a.test", atestFile.getPayload().getName());
// The test remote files are created with the current timestamp + 1 day.
assertThat(atestFile.getPayload().lastModified(), Matchers.greaterThan(System.currentTimeMillis()));
Message<File> btestFile = ms.receive();
assertNotNull(btestFile);
assertEquals("b.test", btestFile.getPayload().getName());
// The test remote files are created with the current timestamp + 1 day.
assertThat(atestFile.getPayload().lastModified(), Matchers.greaterThan(System.currentTimeMillis()));
Message<File> nothing = ms.receive();
assertNull(nothing);
@@ -120,6 +131,10 @@ public class SftpInboundRemoteFileSystemSynchronizerTests {
LsEntry lsEntry = mock(LsEntry.class);
SftpATTRS attributes = mock(SftpATTRS.class);
when(lsEntry.getAttrs()).thenReturn(attributes);
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, 1);
when(lsEntry.getAttrs().getMTime()).thenReturn(new Long(calendar.getTimeInMillis() / 1000).intValue());
when(lsEntry.getFilename()).thenReturn(fileName);
sftpEntries.add(lsEntry);
when(channel.get("remote-test-dir/"+fileName)).thenReturn(new FileInputStream("remote-test-dir/" + fileName));

View File

@@ -144,8 +144,7 @@ public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyPro
}
@Override
public void onInit() {
super.onInit();
protected void doInit() {
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
Assert.state(this.destinationProvider == null || CollectionUtils.isEmpty(this.uriVariableExpressions),
"uri variables are not supported when a DestinationProvider is supplied.");

View File

@@ -187,6 +187,43 @@ public interface Cafe {
</para>
</section>
<section id="gateway-mapping">
<title>Mapping Method Arguments to a Message</title>
<para>
Using the configuration techniques in the previous section allows control of how method arguments are mapped
to message elements (payload and header(s)). When no explicit configuration is used, certain conventions are
used to perform the mapping. In some cases, these conventions cannot determine which argument is the payload
and which should be mapped to headers.
</para>
<programlisting language="java"><![CDATA[
public String send1(Object foo, Map bar);
public String send2(Map foo, Map bar);
]]></programlisting>
<para>
In the first case, the convention will map the first argument to the payload (as long as it is not a
<code>Map</code>) and the contents of the second become headers.
</para>
<para>
In the second case (or the first when the argument for parameter <code>foo</code> is a <code>Map</code>),
the framework cannot determine
which argument should be the payload; mapping will fail. This can generally be resolved using a
<code>payload-expression</code>, a <code>@Payload</code> annotation and/or a <code>@Headers</code>
annotation.
</para>
<para>
Alternatively, and whenever the conventions break down, you can take the entire responsibility for
mapping the method calls to messages. To do this, implement an
<classname>MethodArgsMessageMapper</classname> and provide it to the
<code>&lt;gateway/&gt;</code> using the <code>mapper</code> attribute. The mapper maps a
<classname>MethodArgsHolder</classname>, which is a simple class wrapping the <classname>java.reflect.Method</classname>
instance and an <code>Object[]</code> containing the arguments. When providing a custom mapper,
the <code>default-payload-expression</code> attribute and <code>&lt;default-header/&gt;</code> elements
are not allowed on the gateway; similarly, the <code>payload-expression</code> attribute and
<code>&lt;header/&gt;</code> elements are not allowed on any <code>&lt;method/&gt;</code> elements.
</para>
</section>
<section id="gateway-calling-no-argument-methods">
<title>Invoking No-Argument Methods</title>
<para>

View File

@@ -354,9 +354,57 @@ By default the HTTP request will be generated using an instance of <classname>Si
</listitem>
</itemizedlist>
<para>
<para>
Since <emphasis>Spring Integration 3.0</emphasis>, in addition to the existing
<code>#pathVariables</code> and <code>#requestParams</code> variables being available in payload and header
expressions, other useful variables have been added.
</para>
<para>
The entire list of available expression variables:
</para>
<itemizedlist>
<listitem>
<emphasis>#requestParams</emphasis> - the <interfacename>MultiValueMap</interfacename> from the
<interfacename>ServletRequest</interfacename> <code>parameterMap</code>.
</listitem>
<listitem>
<emphasis>#pathVariables</emphasis> - the <interfacename>Map</interfacename> from URI Template placeholders and their values;
</listitem>
<listitem>
<emphasis>#matrixVariables</emphasis> - the <interfacename>Map</interfacename> of <interfacename>MultiValueMap</interfacename>
according to
<ulink url="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-matrix-variables"
>Spring MVC Specification</ulink>. Note, <emphasis>#matrixVariables</emphasis> require Spring MVC 3.2 or higher;
</listitem>
<listitem>
<emphasis>#requestAttributes</emphasis> - the <interfacename>org.springframework.web.context.request.RequestAttributes</interfacename>
associated with the current Request;
</listitem>
<listitem>
<emphasis>#requestHeaders</emphasis> - the <classname>org.springframework.http.HttpHeaders</classname> object from the current Request;
</listitem>
<listitem>
<emphasis>#cookies</emphasis> - the <interfacename>Map&lt;String, Cookie&gt;</interfacename>
of <classname>javax.servlet.http.Cookie</classname>s from the current Request.
</listitem>
</itemizedlist>
<para>
Note, all these values (and others) can be accessed within expressions in the downstream message
flow via the <classname>ThreadLocal</classname> <interfacename>org.springframework.web.context.request.RequestAttributes</interfacename>
variable, if that message flow is single-threaded and lives within the request thread:
</para>
<programlisting language="xml"><![CDATA[<int-:transformer
expression="T(org.springframework.web.context.request.RequestContextHolder).
requestAttributes.request.queryString"/>
]]></programlisting>
</para>
<para><emphasis>Outbound</emphasis></para>
<para>
To configure the outbound gateway you can use the namespace support as well. The following code snippet shows the different configuration options for an outbound Http gateway. Most importantly, notice that the 'http-method' and 'expected-response-type' are provided. Those are two of the most commonly configured values. The
To configure the outbound gateway you can use the namespace support as well.
The following code snippet shows the different configuration options for an outbound Http gateway.
Most importantly, notice that the 'http-method' and 'expected-response-type' are provided. Those are two of the most commonly configured values. The
default http-method is POST, and the default response type is <emphasis>null</emphasis>. With a null response type, the payload of the reply Message would
contain the ResponseEntity as long as it's http status is a success (non-successful status codes will throw Exceptions).
If you are expecting a different type, such as a <classname>String</classname>, then provide that fully-qualified class name as shown below.

View File

@@ -416,6 +416,7 @@
to configure the associated <classname>Poller</classname> with a
<interfacename>TaskExecutor</interfacename> reference.
</para>
<important>
<para>
Keep in mind, though, that if you use a JDBC backed <emphasis>Message Channel</emphasis> and
you are planning on polling the channel and consequently the message
@@ -426,6 +427,13 @@
threads, may not materialize as expected. For example Apache Derby is
problematic in that regard.
</para>
<para>
To achieve better JDBC queue throughput, and avoid issues when different threads may poll the same
<interfacename>Message</interfacename> from the queue, it is <emphasis role="bold">important</emphasis>
to set the <code>usingIdCache</code> property of <classname>JdbcChannelMessageStore</classname> to <code>true</code>
when using databases that do not support MVCC:
</para>
</important>
<programlisting language="xml"><![CDATA[…
<bean id="queryProvider"
class="o.s.i.jdbc.store.channel.PostgresChannelMessageStoreQueryProvider"/>
@@ -459,6 +467,7 @@
<int:channel id="outputChannel" />
…]]></programlisting>
</section>
<section>
<title>Initializing the Database</title>

View File

@@ -163,6 +163,10 @@
It is now possible to set common headers across all gateway methods, and more options
are provided for adding, to the message, information about which method was invoked.
</listitem>
<listitem>
It is now possible to entirely customize the way that gateway method calls are mapped
to messages.
</listitem>
</itemizedlist>
</para>
<para>
@@ -244,6 +248,10 @@
to be maintained across JVM executions, a custom filter that retains state, perhaps on
the file system, can now be configured.
</para>
<para>
Inbound Channel Adapters now support the <code>preserve-timestamp</code> attribute, which
sets the local file modified timestamp to the timestamp from the server (default false).
</para>
<para>
For more information, see
<xref linkend="ftp-inbound"/> and <xref linkend="sftp-inbound"/>.
@@ -329,12 +337,20 @@
<interfacename>HttpMessageConverter</interfacename>s after the custom message converters.
</listitem>
<listitem>
<emphasis role="bold">'If-(Un)Modified-Since' HTTP headers</emphasis> - previously,
<emphasis role="bold">'If-(Un)Modified-Since' HTTP Headers</emphasis> - previously,
'If-Modified-Since' and 'If-Unmodified-Since' HTTP headers were incorrectly processed
within from/to HTTP headers mapping in the <classname>DefaultHttpHeaderMapper</classname>.
Now, in addition correcting that issue, <classname>DefaultHttpHeaderMapper</classname> provides
date parsing from formatted strings for any HTTP headers that accept date-time values.
</listitem>
<listitem>
<emphasis role="bold">Inbound Endpoint Expression Variables</emphasis> -
In addition to the existing <emphasis>#requestParams</emphasis> and <emphasis>#pathVariables</emphasis>,
the <code>&lt;http:inbound-gateway/&gt;</code> and <code>&lt;http:inbound-channel-adapter/&gt;</code>
now support additional useful variables: <emphasis>#matrixVariables</emphasis>, <emphasis>#requestAttributes</emphasis>,
<emphasis>#requestHeaders</emphasis> and <emphasis>#cookies</emphasis>. These variables are available in
both payload and header expressions.
</listitem>
</itemizedlist>
</para>
<para>