Merge pull request #822 from wwadge/INT-3054
* INT-3054: INT-3054: Add ExpressionEvalMap Support INT-3054 URI Variable Mapping Improvements
This commit is contained in:
@@ -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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* An immutable {@link AbstractMap} implementation that wraps a Map<String, Expression>
|
||||
* and evaluates an {@code expression} for the provided {@code key} from the underlying
|
||||
* {@code original} Map.
|
||||
* </p>
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* 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();
|
||||
* }
|
||||
* </p>
|
||||
* <p>
|
||||
* Thread-safety depends on the original underlying Map.
|
||||
* Objects of this class are not serializable.
|
||||
* </p>
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 3.0
|
||||
*/
|
||||
public final class ExpressionEvalMap extends AbstractMap<String, Object> {
|
||||
|
||||
public static final EvaluationCallback SIMPLE_CALLBACK = new EvaluationCallback() {
|
||||
|
||||
@Override
|
||||
public Object evaluate(Expression expression) {
|
||||
return expression.getValue();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private final Map<String, Expression> original;
|
||||
|
||||
private final EvaluationCallback evaluationCallback;
|
||||
|
||||
private ExpressionEvalMap(Map<String, Expression> 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<Object> values() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return original.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> 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<Map.Entry<String, Object>> 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<String, Expression> 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<String, Expression> 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<String, Expression> 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String, Object> uriVariables = new HashMap<String, Object>();
|
||||
for (Map.Entry<String, Expression> 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<String, Object> 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<URI> uriHolder = new AtomicReference<URI>();
|
||||
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler("http://test/{foo}");
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
Map<String, Expression> multipleExpressions = new HashMap<String, Expression>();
|
||||
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<Object>("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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user