INT-3463: Add encode-uri to ws:outbound-gateway

JIRA: https://jira.spring.io/browse/INT-3463
This commit is contained in:
Artem Bilan
2014-07-04 11:20:36 +03:00
committed by Gary Russell
parent d0fa88ac72
commit e43671feb9
8 changed files with 125 additions and 38 deletions

View File

@@ -18,6 +18,7 @@ package org.springframework.integration.ws;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
@@ -25,6 +26,7 @@ import javax.xml.transform.TransformerException;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.expression.ExpressionEvalMap;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.messaging.Message;
@@ -33,7 +35,8 @@ import org.springframework.messaging.MessageDeliveryException;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.WebServiceMessageFactory;
import org.springframework.ws.client.core.FaultMessageResolver;
@@ -59,7 +62,7 @@ public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyPro
private final WebServiceTemplate webServiceTemplate;
private final UriTemplate uriTemplate;
private final String uri;
private final DestinationProvider destinationProvider;
@@ -71,23 +74,26 @@ public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyPro
private volatile boolean ignoreEmptyResponses = true;
private volatile boolean encodeUri = true;
protected volatile SoapHeaderMapper headerMapper = new DefaultSoapHeaderMapper();
public AbstractWebServiceOutboundGateway(final String uri, WebServiceMessageFactory messageFactory) {
Assert.hasText(uri, "URI must not be empty");
this.webServiceTemplate = new WebServiceTemplate(messageFactory);
this.destinationProvider = null;
this.uriTemplate = new UriTemplate(uri);
this.uri = uri;
}
public AbstractWebServiceOutboundGateway(DestinationProvider destinationProvider, WebServiceMessageFactory messageFactory) {
public AbstractWebServiceOutboundGateway(DestinationProvider destinationProvider,
WebServiceMessageFactory messageFactory) {
Assert.notNull(destinationProvider, "DestinationProvider must not be null");
this.webServiceTemplate = new WebServiceTemplate(messageFactory);
this.destinationProvider = destinationProvider;
// we always call WebServiceTemplate methods with an explicit URI argument,
// but in case the WebServiceTemplate is accessed directly we'll set this:
this.webServiceTemplate.setDestinationProvider(destinationProvider);
this.uriTemplate = null;
this.uri = null;
}
public void setHeaderMapper(SoapHeaderMapper headerMapper) {
@@ -97,7 +103,6 @@ public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyPro
/**
* Set the Map of URI variable expressions to evaluate against the outbound message
* when replacing the variable placeholders in a URI template.
*
* @param uriVariableExpressions The URI variable expressions.
*/
public void setUriVariableExpressions(Map<String, Expression> uriVariableExpressions) {
@@ -107,6 +112,17 @@ public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyPro
}
}
/**
* Specify whether the URI should be encoded after any <code>uriVariables</code>
* are expanded and before sending the request. The default value is <code>true</code>.
* @param encodeUri true if the URI should be encoded.
* @see org.springframework.web.util.UriComponentsBuilder.
* @since 4.1
*/
public void setEncodeUri(boolean encodeUri) {
this.encodeUri = encodeUri;
}
public void setReplyChannel(MessageChannel replyChannel) {
this.setOutputChannel(replyChannel);
}
@@ -115,7 +131,6 @@ public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyPro
* Specify whether empty String response payloads should be ignored.
* The default is <code>true</code>. Set this to <code>false</code> if
* you want to send empty String responses in reply Messages.
*
* @param ignoreEmptyResponses true if empty responses should be ignored.
*/
public void setIgnoreEmptyResponses(boolean ignoreEmptyResponses) {
@@ -159,7 +174,13 @@ public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyPro
@Override
public final Object handleRequestMessage(Message<?> requestMessage) {
URI uri = this.prepareUri(requestMessage);
URI uri = null;
try {
uri = this.prepareUri(requestMessage);
}
catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
if (uri == null) {
throw new MessageDeliveryException(requestMessage, "Failed to determine URI for " +
"Web Service request in outbound gateway: " + this.getComponentName());
@@ -175,22 +196,26 @@ public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyPro
return null;
}
protected abstract Object doHandle(String uri, Message<?> requestMessage, WebServiceMessageCallback requestCallback);
private URI prepareUri(Message<?> requestMessage) {
private URI prepareUri(Message<?> requestMessage) throws URISyntaxException {
if (this.destinationProvider != null) {
return this.destinationProvider.getDestination();
}
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);
}
return this.uriTemplate.expand(uriVariables);
Map<String, Object> uriVariables = ExpressionEvalMap.from(this.uriVariableExpressions)
.usingEvaluationContext(this.evaluationContext)
.withRoot(requestMessage)
.build();
UriComponents uriComponents = UriComponentsBuilder.fromUriString(uri).buildAndExpand(uriVariables);
return this.encodeUri ? uriComponents.toUri() : new URI(uriComponents.toUriString());
}
protected abstract class RequestMessageCallback extends TransformerObjectSupport implements WebServiceMessageCallback {
protected abstract Object doHandle(String uri, Message<?> requestMessage,
WebServiceMessageCallback requestCallback);
protected abstract class RequestMessageCallback extends TransformerObjectSupport
implements WebServiceMessageCallback {
private final WebServiceMessageCallback requestCallback;
@@ -206,8 +231,8 @@ public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyPro
Object payload = this.requestMessage.getPayload();
if (message instanceof SoapMessage){
this.doWithMessageInternal(message, payload);
AbstractWebServiceOutboundGateway.this.headerMapper.fromHeadersToRequest(this.requestMessage.getHeaders(),
(SoapMessage) message);
AbstractWebServiceOutboundGateway.this.headerMapper
.fromHeadersToRequest(this.requestMessage.getHeaders(), (SoapMessage) message);
if (this.requestCallback != null) {
this.requestCallback.doWithMessage(message);
}
@@ -215,11 +240,13 @@ public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyPro
}
public abstract void doWithMessageInternal(WebServiceMessage message, Object payload) throws IOException, TransformerException;
public abstract void doWithMessageInternal(WebServiceMessage message, Object payload)
throws IOException, TransformerException;
}
protected abstract class ResponseMessageExtractor extends TransformerObjectSupport implements WebServiceMessageExtractor<Object> {
protected abstract class ResponseMessageExtractor extends TransformerObjectSupport
implements WebServiceMessageExtractor<Object> {
@Override
public Object extractData(WebServiceMessage message)
@@ -230,7 +257,10 @@ public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyPro
if (resultObject != null && message instanceof SoapMessage){
Map<String, Object> mappedMessageHeaders =
AbstractWebServiceOutboundGateway.this.headerMapper.toHeadersFromReply((SoapMessage) message);
return AbstractWebServiceOutboundGateway.this.getMessageBuilderFactory().withPayload(resultObject).copyHeaders(mappedMessageHeaders).build();
return AbstractWebServiceOutboundGateway.this.getMessageBuilderFactory()
.withPayload(resultObject)
.copyHeaders(mappedMessageHeaders)
.build();
}
else {
return resultObject;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@@ -85,6 +85,7 @@ public class WebServiceOutboundGatewayParser extends AbstractOutboundGatewayPars
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout", "sendTimeout");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "requires-reply");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "ignore-empty-responses");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encode-uri");
this.postProcessGateway(builder, element, parserContext);
IntegrationNamespaceUtils.configureHeaderMapper(element, builder, parserContext, DefaultSoapHeaderMapper.class, null);

View File

@@ -108,6 +108,18 @@
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="encode-uri" default="true">
<xsd:annotation>
<xsd:documentation>
When set to "false", the URI won't be encoded before the request is sent. This may be useful
in some scenarios as it allows user control over the encoding, if needed. Default is "true".
This attribute is ignored, if 'destination-provider' is specified.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="xsd:boolean xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="destination-provider" type="xsd:string">
<xsd:annotation>
<xsd:documentation>

View File

@@ -137,7 +137,7 @@ public class UriVariableTests {
// expected
assertThat(e.getCause(), Matchers.is(Matchers.instanceOf(WebServiceIOException.class))); // offline
}
assertEquals("http://localhost/spring-integration?param=test1%20%26%20test2", uri.get());
assertEquals("http://localhost/spring-integration?param=test1%20&%20test2", uri.get());
}
@Test

View File

@@ -16,11 +16,9 @@
package org.springframework.integration.ws.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.net.URI;
import java.util.List;
import org.junit.Assert;
@@ -30,25 +28,26 @@ import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.endpoint.PollingConsumer;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.ws.MarshallingWebServiceOutboundGateway;
import org.springframework.integration.ws.SimpleWebServiceOutboundGateway;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.web.util.UriTemplate;
import org.springframework.ws.WebServiceMessageFactory;
import org.springframework.ws.client.core.FaultMessageResolver;
import org.springframework.ws.client.core.SourceExtractor;
import org.springframework.ws.client.core.WebServiceMessageCallback;
import org.springframework.ws.client.core.WebServiceMessageExtractor;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.client.support.interceptor.ClientInterceptor;
import org.springframework.ws.transport.WebServiceMessageSender;
@@ -368,7 +367,7 @@ public class WebServiceOutboundGatewayParserTests {
assertEquals(SimpleWebServiceOutboundGateway.class, gateway.getClass());
DirectFieldAccessor accessor = new DirectFieldAccessor(gateway);
assertEquals("Wrong DestinationProvider", stubProvider, accessor.getPropertyValue("destinationProvider"));
assertNull(accessor.getPropertyValue("uriTemplate"));
assertNull(accessor.getPropertyValue("uri"));
Object destinationProviderObject = new DirectFieldAccessor(
accessor.getPropertyValue("webServiceTemplate")).getPropertyValue("destinationProvider");
assertEquals("Wrong DestinationProvider", stubProvider,destinationProviderObject);
@@ -404,8 +403,23 @@ public class WebServiceOutboundGatewayParserTests {
assertEquals(EventDrivenConsumer.class, endpoint.getClass());
MessageHandler handler = TestUtils.getPropertyValue(endpoint, "handler", MessageHandler.class);
assertNull(TestUtils.getPropertyValue(handler, "destinationProvider"));
UriTemplate uriTemplate = TestUtils.getPropertyValue(handler, "uriTemplate", UriTemplate.class);
assertEquals(URI.create("jms:wsQueue"), uriTemplate.expand());
assertFalse(TestUtils.getPropertyValue(handler, "encodeUri", Boolean.class));
WebServiceTemplate webServiceTemplate = TestUtils.getPropertyValue(handler, "webServiceTemplate",
WebServiceTemplate.class);
webServiceTemplate = spy(webServiceTemplate);
doReturn(null).when(webServiceTemplate).sendAndReceive(anyString(),
any(WebServiceMessageCallback.class),
any(WebServiceMessageExtractor.class));
new DirectFieldAccessor(handler).setPropertyValue("webServiceTemplate", webServiceTemplate);
handler.handleMessage(new GenericMessage<String>("foo"));
verify(webServiceTemplate).sendAndReceive(eq("jms:wsQueue"),
any(WebServiceMessageCallback.class),
any(WebServiceMessageExtractor.class));
}
@Test(expected = BeanDefinitionParsingException.class)

View File

@@ -123,6 +123,7 @@
<ws:outbound-gateway id="gatewayWithJmsUri"
request-channel="inputChannel"
encode-uri="false"
uri="jms:wsQueue" />
<bean id="sourceExtractor" class="org.springframework.integration.ws.config.StubSourceExtractor"/>

View File

@@ -38,5 +38,13 @@
See <xref linkend="sms-caution"/> for more information.
</para>
</section>
<section id="4.1-ws-encode-uri">
<title>Web Service Outbound Gateway: encode-uri</title>
<para>
The <code>&lt;ws:outbound-gateway/&gt;</code> now
provides an <code>encode-uri</code> attribute to allow disabling the encoding of the URI object
before sending the request.
</para>
</section>
</section>
</chapter>

View File

@@ -154,5 +154,26 @@ as per standard Spring Web Services configuration.
If a <classname>DestinationProvider</classname> is supplied, variable substitution is not supported
and a configuration error will result if variables are provided.
</para>
<para>
<emphasis>Controlling URI Encoding</emphasis>
</para>
<para>
By default, the URL string is encoded (see
<ulink url="http://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/web/util/UriComponentsBuilder.html">UriComponentsBuilder</ulink>)
to the URI object before sending the request. In some scenarios with a non-standard URI it is
undesirable to perform the encoding. Since <emphasis>version 4.1 </emphasis> the
<code>&lt;ws:outbound-gateway/&gt;</code> provides an <code>encode-uri</code> attribute.
To disable encoding the URL, this attribute should be set to <code>false</code> (by default it is <code>true</code>).
If you wish to partially encode some of the URL, this can be achieved using an <code>expression</code> within a
<code>&lt;uri-variable/&gt;</code>:
<programlisting
language="xml"><![CDATA[<ws:outbound-gateway url="http://somehost/%2f/fooApps?bar={param}" encode-uri="false">
<http:uri-variable name="param"
expression="T(org.apache.commons.httpclient.util.URIUtil)
.encodeWithinQuery('Hellow World!')"/>
</ws:outbound-gateway>]]></programlisting>
Note, <code>encode-uri</code> is ignored, if <classname>DestinationProvider</classname> is supplied.
</para>
</section>
</chapter>