diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java index 9ee1f3937f..c393baeb98 100644 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java +++ b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java @@ -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) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractStandardMessageHandlerFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractStandardMessageHandlerFactoryBean.java index 9fa35003ae..689cd16a8c 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractStandardMessageHandlerFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractStandardMessageHandlerFactoryBean.java @@ -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); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/GatewayParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/GatewayParser.java index dea4f53dac..5a2d6bf525 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/GatewayParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/GatewayParser.java @@ -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 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()); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java index 0c15ed6248..150a3dc054 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java @@ -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"); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java b/spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java index 0a95cbff97..d234ae6008 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java @@ -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()); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapper.java index affa33ab40..bcf624e239 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapper.java @@ -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 parameterList; + private final MethodArgsMessageMapper argsMapper; + private volatile Expression payloadExpression; private final Map parameterPayloadExpressions = new HashMap(); @@ -93,23 +96,28 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper headerExpressions) { - this(method, headerExpressions, null); + this(method, headerExpressions, null, null); } public GatewayMethodInboundMessageMapper(Method method, Map headerExpressions, - Map globalHeaderExpressions) { + Map 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 mapArgumentsToMessage(Object[] arguments) { - Object messageOrPayload = null; - boolean foundPayloadAnnotation = false; - Map headers = new HashMap(); - 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 evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, this.headerExpressions); - builder.copyHeaders(evaluatedHeaders); - } - // ...whereas global (default) headers do not... - if (!CollectionUtils.isEmpty(this.globalHeaderExpressions)) { - Map evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, this.globalHeaderExpressions); - builder.copyHeadersIfAbsent(evaluatedHeaders); - } - return builder.build(); } private Map evaluateHeaders(EvaluationContext methodInvocationEvaluationContext, Map headerExpressions) { @@ -318,4 +258,94 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper toMessage(MethodArgsHolder holder) throws Exception { + Object messageOrPayload = null; + boolean foundPayloadAnnotation = false; + Object[] arguments = holder.getArgs(); + EvaluationContext methodInvocationEvaluationContext = createMethodInvocationEvaluationContext(arguments); + Map headers = new HashMap(); + 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 evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, + GatewayMethodInboundMessageMapper.this.headerExpressions); + builder.copyHeaders(evaluatedHeaders); + } + // ...whereas global (default) headers do not... + if (!CollectionUtils.isEmpty(GatewayMethodInboundMessageMapper.this.globalHeaderExpressions)) { + Map evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, + GatewayMethodInboundMessageMapper.this.globalHeaderExpressions); + builder.copyHeadersIfAbsent(evaluatedHeaders); + } + return builder.build(); + } + + } + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java index 4ccd9500f0..affbca8f65 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java @@ -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); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsHolder.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsHolder.java new file mode 100644 index 0000000000..0a8ce4c792 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsHolder.java @@ -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; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsMessageMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsMessageMapper.java new file mode 100644 index 0000000000..70df6db7c9 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsMessageMapper.java @@ -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 { + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractReplyProducingMessageHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractReplyProducingMessageHandler.java index 95ab54ba3b..095ff0c0ec 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractReplyProducingMessageHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractReplyProducingMessageHandler.java @@ -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() { } /** diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java index bc532b9ce1..32c3aaa79d 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java @@ -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(); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/ServiceActivatingHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/ServiceActivatingHandler.java index 06f884687a..94bc01b4e7 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/ServiceActivatingHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/ServiceActivatingHandler.java @@ -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()); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/splitter/AbstractMessageProcessingSplitter.java b/spring-integration-core/src/main/java/org/springframework/integration/splitter/AbstractMessageProcessingSplitter.java index f7fc3f66ec..5150192d43 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/splitter/AbstractMessageProcessingSplitter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/splitter/AbstractMessageProcessingSplitter.java @@ -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); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/ContentEnricher.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/ContentEnricher.java index 73234a3244..c3f87d4628 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/transformer/ContentEnricher.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/ContentEnricher.java @@ -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"); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/MessageTransformingHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/MessageTransformingHandler.java index 2458cad1d0..caacdadc6d 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/transformer/MessageTransformingHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/MessageTransformingHandler.java @@ -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()); } diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd index ffd8f12f03..00dc093ed1 100644 --- a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd +++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd @@ -717,6 +717,22 @@ + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerParserTests.java index 3a5cd1a42c..8e3ff3d63a 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerParserTests.java @@ -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()); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/core/MessageIdGenerationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/core/MessageIdGenerationTests.java index e068cafea3..fff47d908d 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/core/MessageIdGenerationTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/core/MessageIdGenerationTests.java @@ -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(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(); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests-context.xml index 788bd10290..ce665b5115 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests-context.xml @@ -20,4 +20,10 @@ + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java index c49557cd20..8078a87536 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java @@ -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(); + } + + } } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapperToMessageTests.java b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapperToMessageTests.java index 5f1bdbc57f..46dc28b812 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapperToMessageTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapperToMessageTests.java @@ -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); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/ServiceActivatorDefaultFrameworkMethodTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/ServiceActivatorDefaultFrameworkMethodTests.java index fba9d6c58f..b6766077e7 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/handler/ServiceActivatorDefaultFrameworkMethodTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/ServiceActivatorDefaultFrameworkMethodTests.java @@ -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); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java index f61de07d68..8047ba6b38 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java @@ -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)); diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java b/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java index c19902314f..a8a6dbee69 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java @@ -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()); diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileInboundChannelAdapterParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileInboundChannelAdapterParser.java index 811fa53248..38e14b2141 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileInboundChannelAdapterParser.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileInboundChannelAdapterParser.java @@ -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); diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java index 728c2a2461..f182e46633 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java @@ -286,8 +286,7 @@ public abstract class AbstractRemoteFileOutboundGateway 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)) { diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/synchronizer/AbstractInboundFileSynchronizer.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/synchronizer/AbstractInboundFileSynchronizer.java index 559d7c4346..0ccb791287 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/synchronizer/AbstractInboundFileSynchronizer.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/synchronizer/AbstractInboundFileSynchronizer.java @@ -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 implements InboundFileSynchronizer, @@ -89,6 +89,12 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS */ private volatile boolean deleteRemoteFiles; + /** + * Should we transfer the remote file timestamp + * 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 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 implements InboundFileS Session 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 filteredFiles = this.filterFiles(files); @@ -192,7 +202,6 @@ public abstract class AbstractInboundFileSynchronizer 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 implements InboundFileS } } finally { - try { - if (inputStream != null) { - inputStream.close(); - } - } - catch (Exception ignored1) { - } try { fileOutputStream.close(); } @@ -228,6 +230,9 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS } } } + if (this.preserveTimestamp) { + localFile.setLastModified(getModified(remoteFile)); + } } } @@ -242,4 +247,6 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS protected abstract String getFilename(F file); + protected abstract long getModified(F file); + } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java index 68636df04e..62a776950d 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java @@ -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) { diff --git a/spring-integration-ftp/.gitignore b/spring-integration-ftp/.gitignore index b97386c397..2fa3db79d5 100644 --- a/spring-integration-ftp/.gitignore +++ b/spring-integration-ftp/.gitignore @@ -1,2 +1,3 @@ local-test-dir/*.test remote-target-dir/*test +test diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/inbound/FtpInboundFileSynchronizer.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/inbound/FtpInboundFileSynchronizer.java index a5b942cae6..1bcca21604 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/inbound/FtpInboundFileSynchronizer.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/inbound/FtpInboundFileSynchronizer.java @@ -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 { @@ -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(); + } + } diff --git a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-3.0.xsd b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-3.0.xsd index 05eaa220f1..39dce04a13 100644 --- a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-3.0.xsd +++ b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-3.0.xsd @@ -235,7 +235,7 @@ - + Specify whether to delete the remote source @@ -245,6 +245,16 @@ + + + + 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. + + + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests-fail-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests-fail-context.xml index 9aa1fc8537..038b9e9b04 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests-fail-context.xml +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests-fail-context.xml @@ -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"> @@ -19,10 +16,10 @@ - - + - + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests.java index 46e5211c4b..4ff9043190 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserInboundTests.java @@ -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 diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests-context.xml index 6d2732ec76..4b6f73f98f 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests-context.xml +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests-context.xml @@ -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"> - - + - + @@ -48,13 +49,13 @@ - + @@ -76,7 +77,7 @@ - + @@ -89,7 +90,7 @@ - + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests.java index 22cd2205df..e1f8082208 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests.java @@ -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); diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTests.java index 335cd88560..d5dc7d5647 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTests.java @@ -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 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 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 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); } diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyCommandMessageProcessor.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyCommandMessageProcessor.java index e75d331fc8..2bb78af57f 100644 --- a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyCommandMessageProcessor.java +++ b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyCommandMessageProcessor.java @@ -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; diff --git a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessor.java b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessor.java index 0230a215cf..3c69524a0d 100644 --- a/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessor.java +++ b/spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessor.java @@ -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 implements InitializingBean { +public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecutingMessageProcessor { - 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 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 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); } } + } diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyExpressionTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyExpressionTests.java deleted file mode 100644 index db9f2abc65..0000000000 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyExpressionTests.java +++ /dev/null @@ -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 completionService = new ExecutorCompletionService(Executors.newFixedThreadPool(10)); - for (int i = 0; i < 100; i++) { - final String name = "bar" + i; - completionService.submit(new Callable() { - 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 set = new HashSet(); - 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 completionService = new ExecutorCompletionService(Executors.newFixedThreadPool(10)); - for (int i = 0; i < 100; i++) { - final String name = "bar" + i; - completionService.submit(new Callable() { - 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 set = new HashSet(); - 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 completionService = new ExecutorCompletionService(Executors.newFixedThreadPool(5)); - for (int i = 0; i < 100; i++) { - final String name = "Bar" + i; - completionService.submit(new Callable() { - 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 set = new HashSet(); - for (int i = 0; i < 100; i++) { - set.add(completionService.take().get()); - } - assertEquals(100, set.size()); - } - - - private static class Customizer implements GroovyObjectCustomizer { - - private Map map = new HashMap(); - - public Customizer(Map map) { - super(); - this.map.putAll(map); - } - - public void customize(GroovyObject goo) { - Assert.state(goo instanceof Script, "Expected a Script"); - for (Map.Entry entry : map.entrySet()) { - ((Script) goo).getBinding().setVariable(entry.getKey(), entry.getValue()); - } - } - - public void setMap(Map 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; - } - - } - -} diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessorTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessorTests.java index b92ca96deb..888a70b7cb 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessorTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessorTests.java @@ -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("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 processor = + new GroovyScriptExecutingMessageProcessor(scriptSource, new ScriptVariableGenerator() { + @Override + public Map generateScriptVariables(Message message) { + Map variables = new HashMap(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 { diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests.java index 34bc3d310d..da29e6134a 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests.java @@ -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 fakeRequest = new HashMap(); + private final Map fakeRequest = new HashMap(); public Object getAttribute(String name, int scope) { return fakeRequest.get(name); diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests.java index 09162cb1e4..9928948642 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyFilterTests.java @@ -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); diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyRouterTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyRouterTests.java index f5eef39c58..4a16de5e16 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyRouterTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyRouterTests.java @@ -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 diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests.java index d0c42022f4..580f4d42aa 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyServiceActivatorTests.java @@ -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")); diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovySplitterTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovySplitterTests.java index 2a1982bdd0..6b16dff3bc 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovySplitterTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovySplitterTests.java @@ -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); } diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyTransformerTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyTransformerTests.java index 56c32321df..d1ddeddd46 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyTransformerTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyTransformerTests.java @@ -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); } } diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java index cd02d4a682..fe63fa1273 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java @@ -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 #pathVariables and - * #queryParameters variables if present. Those variables' values are Maps. + * and it may contain variables: + *
    + *
  • #pathVariables
  • + *
  • #requestParams
  • + *
  • #requestAttributes
  • + *
  • #requestHeaders
  • + *
  • #matrixVariables
  • + *
  • #cookies + *
*/ 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 #pathVariables and - * #queryParameters variables if present. Those variables' values are Maps. + * and it may contain variables: + *
    + *
  • #pathVariables
  • + *
  • #requestParams
  • + *
  • #requestAttributes
  • + *
  • #requestHeaders
  • + *
  • #matrixVariables
  • + *
  • #cookies + *
*/ public void setHeaderExpressions(Map 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 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 cookies = new HashMap(requestCookies.length); + for (Cookie requestCookie : requestCookies) { + cookies.put(requestCookie.getName(), requestCookie); + } + evaluationContext.setVariable("cookies", cookies); + } + Map pathVariables = (Map) 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> matrixVariables = + (Map>) servletRequest.getAttribute(HandlerMapping.class.getName() + ".matrixVariables"); + + if (!CollectionUtils.isEmpty(matrixVariables)) { + if (logger.isDebugEnabled()) { + logger.debug("Mapped matrix variables: " + matrixVariables); + } + evaluationContext.setVariable("matrixVariables", matrixVariables); + } + Map headers = this.headerMapper.toHeaders(request.getHeaders()); Object payload = null; if (this.payloadExpression != null) { diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java b/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java index e3e115040c..4d698667ee 100755 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java @@ -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(); diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/AbstractHttpInboundTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/AbstractHttpInboundTests.java new file mode 100644 index 0000000000..044bdcdf2b --- /dev/null +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/AbstractHttpInboundTests.java @@ -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(); + } + +} diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests.java index 2ec9f33b82..55975afce2 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/HttpProxyScenarioTests.java @@ -137,6 +137,7 @@ public class HttpProxyScenarioTests { assertEquals(ifModifiedSince, headers.get("If-Modified-Since")); assertEquals(ifUnmodifiedSince, headers.get("If-Unmodified-Since")); + RequestContextHolder.resetRequestAttributes(); } } diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java index 629c11359e..5745e06bed 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java @@ -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; diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingControllerTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingControllerTests.java index 538822d274..052e665489 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingControllerTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingControllerTests.java @@ -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 { diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayTests.java index 1a87905b56..7ef211cbdb 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayTests.java @@ -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") diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java index 2c4f1e19f8..73c6c20aab 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java @@ -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(); diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests-context.xml index b2516af13a..2ff049b4e7 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests-context.xml +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/Int2312RequestMappingIntegrationTests-context.xml @@ -26,8 +26,16 @@ request-channel="toLowerCaseChannel" payload-expression="#pathVariables.value"> + + + + + + + + params = new HashMap(); + 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 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) 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 cookies = (Map) 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 diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractClientConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractClientConnectionFactory.java index e3f2c91868..700b54c865 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractClientConnectionFactory.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractClientConnectionFactory.java @@ -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. diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionSupport.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionSupport.java index 391bc6477e..64255a7695 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionSupport.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionSupport.java @@ -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()); } diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetClientConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetClientConnectionFactory.java index 6acaee17cf..7c02f261a2 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetClientConnectionFactory.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetClientConnectionFactory.java @@ -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(), diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetServerConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetServerConnectionFactory.java index 631628ca67..0504f2f693 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetServerConnectionFactory.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetServerConnectionFactory.java @@ -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"); diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioClientConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioClientConnectionFactory.java index ff2ec2c4ea..6935fbdd3b 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioClientConnectionFactory.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioClientConnectionFactory.java @@ -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( diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioServerConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioServerConnectionFactory.java index aa882c5e46..6b97ac6bb0 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioServerConnectionFactory.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioServerConnectionFactory.java @@ -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); diff --git a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/ConnectionToConnectionTests-context.xml b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/ConnectionToConnectionTests-context.xml index dc8857782c..e762803a6e 100644 --- a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/ConnectionToConnectionTests-context.xml +++ b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/ConnectionToConnectionTests-context.xml @@ -13,9 +13,9 @@ - - - + + + + + + diff --git a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/ConnectionToConnectionTests.java b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/ConnectionToConnectionTests.java index fdc5ab48fe..7581bac283 100644 --- a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/ConnectionToConnectionTests.java +++ b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/ConnectionToConnectionTests.java @@ -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 eventMessage; while ((eventMessage = (Message) 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(); } diff --git a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/ConnectionEventTests.java b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/ConnectionEventTests.java index 546b9230ab..4888a3dd60 100644 --- a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/ConnectionEventTests.java +++ b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/ConnectionEventTests.java @@ -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 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**")); } } diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcOutboundGateway.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcOutboundGateway.java index 8592ca3005..04b547a0fc 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcOutboundGateway.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcOutboundGateway.java @@ -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); diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/StoredProcOutboundGateway.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/StoredProcOutboundGateway.java index bc1e8ee37a..f3ff695d87 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/StoredProcOutboundGateway.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/StoredProcOutboundGateway.java @@ -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 diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java index 607b55b07d..40c35b0cc9 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java @@ -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; } /** diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcOutboundGatewayTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcOutboundGatewayTests.java index f8f128a658..955e6aa016 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcOutboundGatewayTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcOutboundGatewayTests.java @@ -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()); diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/AbstractTxTimeoutMessageStoreTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/AbstractTxTimeoutMessageStoreTests.java index 09ed756431..0201ce4656 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/AbstractTxTimeoutMessageStoreTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/AbstractTxTimeoutMessageStoreTests.java @@ -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("test")); + } + + assertTrue(this.successfulLatch.await(5, TimeUnit.SECONDS)); + + assertEquals(0, errorAtomicInteger.get()); + } + } diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/DerbyTxTimeoutMessageStoreTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/DerbyTxTimeoutMessageStoreTests.java index f48528226d..b954331242 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/DerbyTxTimeoutMessageStoreTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/DerbyTxTimeoutMessageStoreTests.java @@ -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(); + } + } diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/HsqlTxTimeoutMessageStoreTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/HsqlTxTimeoutMessageStoreTests.java index 803a31af9f..f88d4fec3d 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/HsqlTxTimeoutMessageStoreTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/HsqlTxTimeoutMessageStoreTests.java @@ -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(); + } + } diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/MySqlTxTimeoutMessageStoreTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/MySqlTxTimeoutMessageStoreTests.java index b7f816d06f..7eaa27a7f7 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/MySqlTxTimeoutMessageStoreTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/MySqlTxTimeoutMessageStoreTests.java @@ -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(); + } + } diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/TxTimeoutMessageStoreTests-context.xml b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/TxTimeoutMessageStoreTests-context.xml index b4f04f17f0..7276d88ed6 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/TxTimeoutMessageStoreTests-context.xml +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/TxTimeoutMessageStoreTests-context.xml @@ -54,5 +54,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsOutboundGateway.java b/spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsOutboundGateway.java index 32684c0aa1..a128a549a6 100644 --- a/spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsOutboundGateway.java +++ b/spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsOutboundGateway.java @@ -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()); diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java index d24e352adc..c77f7189e7 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java @@ -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 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 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 resolveParameters(Message message) { - Map map = null; - if (message.getPayload() instanceof Map) { - map = (Map) 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 resolveParameters(Message message) { + Map map = null; + if (message.getPayload() instanceof Map) { + map = (Map) 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 createParameterMapFromList(List parameters) { - Map map = new HashMap(); - for (int i = 0; i < parameters.size(); i++) { - map.put("p" + (i + 1), parameters.get(i)); - } - return map; - } + @SuppressWarnings("rawtypes") + private Map createParameterMapFromList(List parameters) { + Map map = new HashMap(); + for (int i = 0; i < parameters.size(); i++) { + map.put("p" + (i + 1), parameters.get(i)); + } + return map; + } } \ No newline at end of file diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/ServiceActivatorDefaultFrameworkMethodTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/ServiceActivatorDefaultFrameworkMethodTests-context.xml new file mode 100644 index 0000000000..a222fe42dc --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/ServiceActivatorDefaultFrameworkMethodTests-context.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/ServiceActivatorDefaultFrameworkMethodTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/ServiceActivatorDefaultFrameworkMethodTests.java new file mode 100644 index 0000000000..90ad33084d --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/ServiceActivatorDefaultFrameworkMethodTests.java @@ -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 { + + private String prefix; + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String processMessage(Message message) { + return prefix + ":" + message.getPayload(); + } + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGateway.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGateway.java index fa75e85832..e0bcc458e5 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGateway.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGateway.java @@ -62,12 +62,8 @@ public class JpaOutboundGateway extends AbstractReplyProducingMessageHandler { } - /** - * - */ @Override - protected void onInit() { - super.onInit(); + protected void doInit() { this.jpaExecutor.setBeanFactory(this.getBeanFactory()); } diff --git a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/AbstractScriptExecutingMessageProcessor.java b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/AbstractScriptExecutingMessageProcessor.java index ce7beed5e8..6fe6aec74c 100644 --- a/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/AbstractScriptExecutingMessageProcessor.java +++ b/spring-integration-scripting/src/main/java/org/springframework/integration/scripting/AbstractScriptExecutingMessageProcessor.java @@ -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 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 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 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 */ diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.java index 8aa35e16a3..bb66db974e 100644 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.java +++ b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/config/jsr223/Jsr223ServiceActivatorTests.java @@ -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")); diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/Jsr223ScriptExecutorTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/Jsr223ScriptExecutorTests.java index dcddde9582..039fac7099 100644 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/Jsr223ScriptExecutorTests.java +++ b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/Jsr223ScriptExecutorTests.java @@ -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 variables = new HashMap(); - + Map headers = new HashMap(); - 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")); } } diff --git a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/PythonScriptExecutorTests.java b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/PythonScriptExecutorTests.java index f30c04b549..41a34dcc47 100644 --- a/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/PythonScriptExecutorTests.java +++ b/spring-integration-scripting/src/test/java/org/springframework/integration/scripting/jsr223/PythonScriptExecutorTests.java @@ -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 variables = new HashMap(); diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizer.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizer.java index 1ee8246995..a6f177aa75 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizer.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/inbound/SftpInboundFileSynchronizer.java @@ -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; + } + } diff --git a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-3.0.xsd b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-3.0.xsd index 24aa57d0d7..061812ef13 100644 --- a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-3.0.xsd +++ b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-3.0.xsd @@ -236,7 +236,7 @@ - + Specify whether to delete the remote source @@ -246,6 +246,16 @@ + + + + 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. + + + diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context.xml index 3e461e1db5..e0ffc0b0dc 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context.xml +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests-context.xml @@ -22,11 +22,11 @@ - + - + @@ -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"> - + @@ -118,7 +119,7 @@ - + diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests.java index bcec30f821..54640f371f 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/InboundChannelAdapterParserTests.java @@ -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); diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java index 8f86df4cc0..fcc128f7e6 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java @@ -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 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 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 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)); diff --git a/spring-integration-ws/src/main/java/org/springframework/integration/ws/AbstractWebServiceOutboundGateway.java b/spring-integration-ws/src/main/java/org/springframework/integration/ws/AbstractWebServiceOutboundGateway.java index bc7b01947e..55de7ac575 100644 --- a/spring-integration-ws/src/main/java/org/springframework/integration/ws/AbstractWebServiceOutboundGateway.java +++ b/spring-integration-ws/src/main/java/org/springframework/integration/ws/AbstractWebServiceOutboundGateway.java @@ -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."); diff --git a/src/reference/docbook/gateway.xml b/src/reference/docbook/gateway.xml index e0f12db1be..28012f740a 100644 --- a/src/reference/docbook/gateway.xml +++ b/src/reference/docbook/gateway.xml @@ -187,6 +187,43 @@ public interface Cafe { +
+ Mapping Method Arguments to a Message + + 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. + + + + In the first case, the convention will map the first argument to the payload (as long as it is not a + Map) and the contents of the second become headers. + + + In the second case (or the first when the argument for parameter foo is a Map), + the framework cannot determine + which argument should be the payload; mapping will fail. This can generally be resolved using a + payload-expression, a @Payload annotation and/or a @Headers + annotation. + + + 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 + MethodArgsMessageMapper and provide it to the + <gateway/> using the mapper attribute. The mapper maps a + MethodArgsHolder, which is a simple class wrapping the java.reflect.Method + instance and an Object[] containing the arguments. When providing a custom mapper, + the default-payload-expression attribute and <default-header/> elements + are not allowed on the gateway; similarly, the payload-expression attribute and + <header/> elements are not allowed on any <method/> elements. + +
+
Invoking No-Argument Methods diff --git a/src/reference/docbook/http.xml b/src/reference/docbook/http.xml index 2a720af4e7..21a162a18a 100644 --- a/src/reference/docbook/http.xml +++ b/src/reference/docbook/http.xml @@ -354,9 +354,57 @@ By default the HTTP request will be generated using an instance of Si + + + Since Spring Integration 3.0, in addition to the existing + #pathVariables and #requestParams variables being available in payload and header + expressions, other useful variables have been added. + + + The entire list of available expression variables: + + + + #requestParams - the MultiValueMap from the + ServletRequest parameterMap. + + + #pathVariables - the Map from URI Template placeholders and their values; + + + #matrixVariables - the Map of MultiValueMap + according to + Spring MVC Specification. Note, #matrixVariables require Spring MVC 3.2 or higher; + + + #requestAttributes - the org.springframework.web.context.request.RequestAttributes + associated with the current Request; + + + #requestHeaders - the org.springframework.http.HttpHeaders object from the current Request; + + + #cookies - the Map<String, Cookie> + of javax.servlet.http.Cookies from the current Request. + + + + Note, all these values (and others) can be accessed within expressions in the downstream message + flow via the ThreadLocal org.springframework.web.context.request.RequestAttributes + variable, if that message flow is single-threaded and lives within the request thread: + + + ]]> + + Outbound - 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 null. 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 String, then provide that fully-qualified class name as shown below. diff --git a/src/reference/docbook/jdbc.xml b/src/reference/docbook/jdbc.xml index e01bed77ec..7c067aa6e9 100644 --- a/src/reference/docbook/jdbc.xml +++ b/src/reference/docbook/jdbc.xml @@ -416,6 +416,7 @@ to configure the associated Poller with a TaskExecutor reference. + Keep in mind, though, that if you use a JDBC backed Message Channel 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. + + To achieve better JDBC queue throughput, and avoid issues when different threads may poll the same + Message from the queue, it is important + to set the usingIdCache property of JdbcChannelMessageStore to true + when using databases that do not support MVCC: + + @@ -459,6 +467,7 @@ …]]> +
Initializing the Database diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 6d3f2ab7b4..5cb22379b3 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -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. + + It is now possible to entirely customize the way that gateway method calls are mapped + to messages. + @@ -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. + + Inbound Channel Adapters now support the preserve-timestamp attribute, which + sets the local file modified timestamp to the timestamp from the server (default false). + For more information, see and . @@ -329,12 +337,20 @@ HttpMessageConverters after the custom message converters. - 'If-(Un)Modified-Since' HTTP headers - previously, + 'If-(Un)Modified-Since' HTTP Headers - previously, 'If-Modified-Since' and 'If-Unmodified-Since' HTTP headers were incorrectly processed within from/to HTTP headers mapping in the DefaultHttpHeaderMapper. Now, in addition correcting that issue, DefaultHttpHeaderMapper provides date parsing from formatted strings for any HTTP headers that accept date-time values. + + Inbound Endpoint Expression Variables - + In addition to the existing #requestParams and #pathVariables, + the <http:inbound-gateway/> and <http:inbound-channel-adapter/> + now support additional useful variables: #matrixVariables, #requestAttributes, + #requestHeaders and #cookies. These variables are available in + both payload and header expressions. +