diff --git a/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionEvalMap.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionEvalMap.java
new file mode 100644
index 0000000000..1e726543a6
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionEvalMap.java
@@ -0,0 +1,306 @@
+/*
+ * 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.expression;
+
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+
+/**
+ *
+ * An immutable {@link AbstractMap} implementation that wraps a Map
+ * and evaluates an {@code expression} for the provided {@code key} from the underlying
+ * {@code original} Map.
+ *
+ *
+ * Any mutating operations ({@link #put(String, Object)}, {@link #remove(Object)} etc.)
+ * are not allowed on instances of this class. Mutation can be performed on underlying Map
+ * if it supports it.
+ *
+ *
+ * A {@link ExpressionEvalMapBuilder} must be used to instantiate this class
+ * via its {@link #from(Map)} method:
+ * {@code
+ * ExpressionEvalMap evalMap = ExpressionEvalMap
+ * .from(expressions)
+ .usingCallback(new EvaluationCallback() {
+ Object evaluate(Expression expression) {
+ // return some expression evaluation
+ }
+ })
+ .build();
+ * }
+ *
+ *
+ * Thread-safety depends on the original underlying Map.
+ * Objects of this class are not serializable.
+ *
+ *
+ * @author Artem Bilan
+ * @since 3.0
+ */
+public final class ExpressionEvalMap extends AbstractMap {
+
+ public static final EvaluationCallback SIMPLE_CALLBACK = new EvaluationCallback() {
+
+ @Override
+ public Object evaluate(Expression expression) {
+ return expression.getValue();
+ }
+
+ };
+
+ private final Map original;
+
+ private final EvaluationCallback evaluationCallback;
+
+ private ExpressionEvalMap(Map original, EvaluationCallback evaluationCallback) {
+ this.original = original;
+ this.evaluationCallback = evaluationCallback;
+ }
+
+ /**
+ * Gets the {@code value}({@link Expression}) for the provided {@code key}
+ * from {@link #original} and returns the result of evaluation using {@link #evaluationCallback}.
+ */
+ @Override
+ public Object get(Object key) {
+ Expression expression = original.get(key);
+ if (expression != null) {
+ return this.evaluationCallback.evaluate(expression);
+ }
+ return null;
+ }
+
+ @Override
+ public Collection values() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return original.containsKey(key);
+ }
+
+ @Override
+ public Set keySet() {
+ return original.keySet();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return original.isEmpty();
+ }
+
+ @Override
+ public int size() {
+ return original.size();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return original.equals(o);
+ }
+
+ @Override
+ public int hashCode() {
+ return original.hashCode();
+ }
+
+ @Override
+ public Set> entrySet() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object put(String key, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void putAll(Map extends String, ? extends Object> m) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object remove(Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ public static ExpressionEvalMapBuilder from(Map expressions) {
+ return new ExpressionEvalMapBuilder(expressions);
+ }
+
+
+ /**
+ * Implementations of this interface can be provided to build 'on demand {@link #get(Object)} logic'
+ * for {@link ExpressionEvalMap}.
+ */
+ public interface EvaluationCallback {
+
+ Object evaluate(Expression expression);
+
+ }
+
+
+ /**
+ * The {@link EvaluationCallback} implementation which evaluates an expression using
+ * the provided {@code context}, {@code root} and {@code returnType} variables.
+ */
+ public static class ComponentsEvaluationCallback implements EvaluationCallback {
+
+ private final EvaluationContext context;
+
+ private final Object root;
+
+ private final Class> returnType;
+
+ public ComponentsEvaluationCallback(EvaluationContext context, Object root, Class> returnType) {
+ this.context = context;
+ this.root = root;
+ this.returnType = returnType;
+ }
+
+ @Override
+ public Object evaluate(Expression expression) {
+ if (this.context != null) {
+ return expression.getValue(this.context, this.root, this.returnType);
+ }
+ return expression.getValue(this.root, this.returnType);
+ }
+
+ }
+
+
+ /**
+ * The builder class to instantiate {@link ExpressionEvalMap}.
+ */
+ public static final class ExpressionEvalMapBuilder {
+
+ private final Map expressions;
+
+ private EvaluationCallback evaluationCallback;
+
+ private EvaluationContext context;
+
+ private Object root;
+
+ private Class> returnType;
+
+ private final ExpressionEvalMapComponentsBuilder evalMapComponentsBuilder = new ExpressionEvalMapComponentsBuilderImpl();
+
+ private final ExpressionEvalMapFinalBuilder finalBuilder = new ExpressionEvalMapFinalBuilderImpl();
+
+ private ExpressionEvalMapBuilder(Map expressions) {
+ this.expressions = expressions;
+ }
+
+ public ExpressionEvalMapFinalBuilder usingCallback(EvaluationCallback callback) {
+ this.evaluationCallback = callback;
+ return finalBuilder;
+ }
+
+ public ExpressionEvalMapFinalBuilder usingSimpleCallback() {
+ return this.usingCallback(SIMPLE_CALLBACK);
+ }
+
+ public ExpressionEvalMapComponentsBuilder usingEvaluationContext(EvaluationContext context) {
+ this.context = context;
+ return this.evalMapComponentsBuilder;
+ }
+
+ public ExpressionEvalMapComponentsBuilder withRoot(Object root) {
+ this.root = root;
+ return this.evalMapComponentsBuilder;
+
+ }
+
+ public ExpressionEvalMapComponentsBuilder withReturnType(Class> returnType) {
+ this.returnType = returnType;
+ return this.evalMapComponentsBuilder;
+
+ }
+
+
+ private class ExpressionEvalMapFinalBuilderImpl implements ExpressionEvalMapFinalBuilder {
+
+ @Override
+ public ExpressionEvalMap build() {
+ if (evaluationCallback != null) {
+ return new ExpressionEvalMap(expressions, evaluationCallback);
+ }
+ return new ExpressionEvalMap(expressions, new ComponentsEvaluationCallback(context, root, returnType));
+ }
+
+ }
+
+
+ private class ExpressionEvalMapComponentsBuilderImpl extends ExpressionEvalMapFinalBuilderImpl
+ implements ExpressionEvalMapComponentsBuilder {
+
+ @Override
+ public ExpressionEvalMapComponentsBuilder usingEvaluationContext(EvaluationContext context) {
+ return ExpressionEvalMapBuilder.this.usingEvaluationContext(context);
+ }
+
+ @Override
+ public ExpressionEvalMapComponentsBuilder withRoot(Object root) {
+ return ExpressionEvalMapBuilder.this.withRoot(root);
+ }
+
+ @Override
+ public ExpressionEvalMapComponentsBuilder withReturnType(Class> returnType) {
+ return ExpressionEvalMapBuilder.this.withReturnType(returnType);
+ }
+
+ }
+
+ }
+
+
+ public interface ExpressionEvalMapFinalBuilder {
+
+ ExpressionEvalMap build();
+
+ }
+
+
+ public interface ExpressionEvalMapComponentsBuilder extends ExpressionEvalMapFinalBuilder {
+
+ ExpressionEvalMapComponentsBuilder usingEvaluationContext(EvaluationContext context);
+
+ ExpressionEvalMapComponentsBuilder withRoot(Object root);
+
+ ExpressionEvalMapComponentsBuilder withReturnType(Class> returnType);
+
+ }
+
+}
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 9de441aaae..d3c5e9b956 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
@@ -48,6 +48,7 @@ import org.springframework.integration.Message;
import org.springframework.integration.MessageHandlingException;
import org.springframework.integration.MessagingException;
import org.springframework.integration.core.MessageHandler;
+import org.springframework.integration.expression.ExpressionEvalMap;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.http.support.DefaultHttpHeaderMapper;
@@ -79,6 +80,7 @@ import org.springframework.web.util.UriComponentsBuilder;
* @author Gary Russell
* @author Gunnar Hillert
* @author Artem Bilan
+ * @author Wallace Wadge
* @since 2.0
*/
public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMessageHandler {
@@ -338,24 +340,23 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
String uri = this.uriExpression.getValue(this.evaluationContext, requestMessage, String.class);
Assert.notNull(uri, "URI Expression evaluation cannot result in null");
try {
- Map uriVariables = new HashMap();
- for (Map.Entry entry : this.uriVariableExpressions.entrySet()) {
- Object value = entry.getValue().getValue(this.evaluationContext, requestMessage, String.class);
- uriVariables.put(entry.getKey(), value);
- }
-
HttpMethod httpMethod = this.determineHttpMethod(requestMessage);
if (!this.shouldIncludeRequestBody(httpMethod) && this.extractPayloadExplicitlySet){
if (logger.isWarnEnabled()){
logger.warn("The 'extractPayload' attribute has no relevance for the current request since the HTTP Method is '" +
- httpMethod + "', and no request body will be sent for that method.");
+ httpMethod + "', and no request body will be sent for that method.");
}
}
Class> expectedResponseType = this.determineExpectedResponseType(requestMessage);
HttpEntity> httpRequest = this.generateHttpRequest(requestMessage, httpMethod);
+ Map uriVariables = ExpressionEvalMap
+ .from(this.uriVariableExpressions)
+ .usingEvaluationContext(this.evaluationContext)
+ .withRoot(requestMessage)
+ .build();
UriComponents uriComponents = UriComponentsBuilder.fromUriString(uri).buildAndExpand(uriVariables);
URI realUri = this.encodeUri ? uriComponents.toUri() : new URI(uriComponents.toUriString());
ResponseEntity> httpResponse = this.restTemplate.exchange(realUri, httpMethod, httpRequest, expectedResponseType);
@@ -554,6 +555,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
return HttpMethod.valueOf(strHttpMethod);
}
+
private Class> determineExpectedResponseType(Message> requestMessage) throws Exception{
Class> expectedResponseType = null;
String expectedResponseTypeName = null;
@@ -564,5 +566,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
expectedResponseType = ClassUtils.forName(expectedResponseTypeName, ClassUtils.getDefaultClassLoader());
}
return expectedResponseType;
+
}
+
}
diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/UriVariableExpressionTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/UriVariableExpressionTests.java
index b974837838..976d6695ab 100644
--- a/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/UriVariableExpressionTests.java
+++ b/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/UriVariableExpressionTests.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.
@@ -21,10 +21,13 @@ import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
+import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
@@ -35,6 +38,7 @@ import org.springframework.integration.message.GenericMessage;
/**
* @author Dave Syer
* @author Mark Fisher
+ * @author Wallace Wadge
* @since 2.0
*/
public class UriVariableExpressionTests {
@@ -65,4 +69,33 @@ public class UriVariableExpressionTests {
assertEquals("http://test/bar", uriHolder.get().toString());
}
+ /** Test for INT-3054: Do not break if there are extra uri variables defined in the http outbound gateway. */
+ @Test
+ public void testFromMessageWithSuperfluousExpressionsInt3054() throws Exception {
+ final AtomicReference uriHolder = new AtomicReference();
+ HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler("http://test/{foo}");
+ SpelExpressionParser parser = new SpelExpressionParser();
+ Map multipleExpressions = new HashMap();
+ multipleExpressions.put("foo", parser.parseExpression("payload"));
+ multipleExpressions.put("extra-to-be-ignored", parser.parseExpression("headers.extra"));
+ handler.setUriVariableExpressions(multipleExpressions);
+ handler.setRequestFactory(new SimpleClientHttpRequestFactory() {
+ @Override
+ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
+ uriHolder.set(uri);
+ throw new RuntimeException("intentional");
+ }
+ });
+ Message> message = new GenericMessage("bar");
+ Exception exception = null;
+ try {
+ handler.handleMessage(message);
+ }
+ catch (Exception e) {
+ exception = e;
+ }
+ assertEquals("intentional", exception.getCause().getMessage());
+ assertEquals("http://test/bar", uriHolder.get().toString());
+ }
+
}