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 1b61a8a336..1a5214a1ea 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 @@ -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 uriVariableExpressions) { @@ -107,6 +112,17 @@ public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyPro } } + /** + * Specify whether the URI should be encoded after any uriVariables + * are expanded and before sending the request. The default value is true. + * @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 true. Set this to false 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 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); - } - return this.uriTemplate.expand(uriVariables); + + Map 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 { + protected abstract class ResponseMessageExtractor extends TransformerObjectSupport + implements WebServiceMessageExtractor { @Override public Object extractData(WebServiceMessage message) @@ -230,7 +257,10 @@ public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyPro if (resultObject != null && message instanceof SoapMessage){ Map 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; diff --git a/spring-integration-ws/src/main/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParser.java b/spring-integration-ws/src/main/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParser.java index ccb8d77e7d..d02edb28cb 100644 --- a/spring-integration-ws/src/main/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParser.java +++ b/spring-integration-ws/src/main/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParser.java @@ -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); diff --git a/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-4.1.xsd b/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-4.1.xsd index ae2bd7289d..a393701dee 100644 --- a/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-4.1.xsd +++ b/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-4.1.xsd @@ -108,6 +108,18 @@ ]]> + + + + 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. + + + + + + diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/UriVariableTests.java b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/UriVariableTests.java index 1f9b65a605..9b663ed1c0 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/UriVariableTests.java +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/UriVariableTests.java @@ -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 diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests.java b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests.java index e2db5f0e92..7ad2478dfd 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests.java +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests.java @@ -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("foo")); + + verify(webServiceTemplate).sendAndReceive(eq("jms:wsQueue"), + any(WebServiceMessageCallback.class), + any(WebServiceMessageExtractor.class)); } @Test(expected = BeanDefinitionParsingException.class) diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/simpleWebServiceOutboundGatewayParserTests.xml b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/simpleWebServiceOutboundGatewayParserTests.xml index 49485a95ce..4231b11ce1 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/simpleWebServiceOutboundGatewayParserTests.xml +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/simpleWebServiceOutboundGatewayParserTests.xml @@ -123,6 +123,7 @@ diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 2c92757762..c16602b624 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -38,5 +38,13 @@ See for more information. +
+ Web Service Outbound Gateway: encode-uri + + The <ws:outbound-gateway/> now + provides an encode-uri attribute to allow disabling the encoding of the URI object + before sending the request. + +
diff --git a/src/reference/docbook/ws.xml b/src/reference/docbook/ws.xml index b963fcd74a..043f4efc75 100644 --- a/src/reference/docbook/ws.xml +++ b/src/reference/docbook/ws.xml @@ -154,5 +154,26 @@ as per standard Spring Web Services configuration. If a DestinationProvider is supplied, variable substitution is not supported and a configuration error will result if variables are provided. + + Controlling URI Encoding + + + By default, the URL string is encoded (see + UriComponentsBuilder) + to the URI object before sending the request. In some scenarios with a non-standard URI it is + undesirable to perform the encoding. Since version 4.1 the + <ws:outbound-gateway/> provides an encode-uri attribute. + To disable encoding the URL, this attribute should be set to false (by default it is true). + If you wish to partially encode some of the URL, this can be achieved using an expression within a + <uri-variable/>: + + +
]]> + Note, encode-uri is ignored, if DestinationProvider is supplied. + +