From faee3d36acbd06b16af8ea5fb29df0bef4d6effa Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Fri, 19 Dec 2025 12:23:28 -0500 Subject: [PATCH] GH-10574: Rely on `WebServiceTemplate.defaultUri` Fixes: https://github.com/spring-projects/spring-integration/issues/10574 * Change the logic of the `AbstractWebServiceOutboundGateway` to take into account a `WebServiceTemplate.defaultUri` option as an alternative to the explicit `uri` or `destinationProvider` * Expose `WebServiceTemplate`-based ctor in the `SimpleWebServiceOutboundGateway` (The `MarshallingWebServiceOutboundGateway` already has one) * Adjust tests and XML parser to cover a new logic * Mention the change in docs --- .../WebServiceOutboundGatewayParser.java | 6 +++-- .../AbstractWebServiceOutboundGateway.java | 27 ++++++++++++++----- .../SimpleWebServiceOutboundGateway.java | 23 ++++++++++++++++ ...viceOutboundGatewayParserTests-context.xml | 5 ++-- .../WebServiceOutboundGatewayParserTests.java | 7 +++-- ...ayWithNeitherUriNorDestinationProvider.xml | 18 +++++-------- .../integration/ws/dsl/WsDslTests.java | 14 +++++----- .../antora/modules/ROOT/pages/whats-new.adoc | 5 ++++ .../antora/modules/ROOT/pages/ws.adoc | 1 + 9 files changed, 74 insertions(+), 32 deletions(-) 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 a3ec44e6377..5271b1d55d9 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 @@ -58,7 +58,7 @@ protected BeanDefinitionBuilder parseHandler(Element element, ParserContext pars String uri = element.getAttribute("uri"); String destinationProvider = element.getAttribute("destination-provider"); List uriVariableElements = DomUtils.getChildElementsByTagName(element, "uri-variable"); - if (StringUtils.hasText(destinationProvider) == StringUtils.hasText(uri)) { + if (StringUtils.hasText(destinationProvider) && StringUtils.hasText(uri)) { parserContext.getReaderContext().error( "Exactly one of 'uri' or 'destination-provider' is required.", element); } @@ -140,7 +140,9 @@ protected void postProcessGateway(BeanDefinitionBuilder builder, Element element } } - private void parseMarshallerAttribute(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) { + private static void parseMarshallerAttribute(BeanDefinitionBuilder builder, Element element, + ParserContext parserContext) { + String marshallerRef = element.getAttribute("marshaller"); String unmarshallerRef = element.getAttribute("unmarshaller"); if (StringUtils.hasText(marshallerRef)) { diff --git a/spring-integration-ws/src/main/java/org/springframework/integration/ws/outbound/AbstractWebServiceOutboundGateway.java b/spring-integration-ws/src/main/java/org/springframework/integration/ws/outbound/AbstractWebServiceOutboundGateway.java index f5230a6719e..1e1873601cc 100644 --- a/spring-integration-ws/src/main/java/org/springframework/integration/ws/outbound/AbstractWebServiceOutboundGateway.java +++ b/spring-integration-ws/src/main/java/org/springframework/integration/ws/outbound/AbstractWebServiceOutboundGateway.java @@ -69,14 +69,14 @@ public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyPro private final Lock lock = new ReentrantLock(); - protected final DefaultUriBuilderFactory uriFactory = new DefaultUriBuilderFactory(); // NOSONAR - final - - private final @Nullable String uri; + protected final DefaultUriBuilderFactory uriFactory = new DefaultUriBuilderFactory(); private final @Nullable DestinationProvider destinationProvider; private final Map uriVariableExpressions = new HashMap<>(); + private @Nullable String uri; + @SuppressWarnings("NullAway.Init") private StandardEvaluationContext evaluationContext; @@ -90,8 +90,9 @@ public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyPro private boolean webServiceTemplateExplicitlySet; - public AbstractWebServiceOutboundGateway(@Nullable final String uri, @Nullable WebServiceMessageFactory messageFactory) { - Assert.hasText(uri, "URI must not be empty"); + public AbstractWebServiceOutboundGateway(@Nullable final String uri, + @Nullable WebServiceMessageFactory messageFactory) { + this.webServiceTemplate = messageFactory != null ? new WebServiceTemplate(messageFactory) : new WebServiceTemplate(); this.destinationProvider = null; @@ -106,11 +107,16 @@ public AbstractWebServiceOutboundGateway(DestinationProvider destinationProvider new WebServiceTemplate(messageFactory) : new WebServiceTemplate(); 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: + // but in case the WebServiceTemplate is accessed directly, we'll set this: this.webServiceTemplate.setDestinationProvider(destinationProvider); this.uri = null; } + public AbstractWebServiceOutboundGateway(WebServiceTemplate webServiceTemplate) { + doSetWebServiceTemplate(webServiceTemplate); + this.destinationProvider = null; + } + public void setHeaderMapper(SoapHeaderMapper headerMapper) { Assert.notNull(headerMapper, "'headerMapper' must not be null"); this.headerMapper = headerMapper; @@ -165,6 +171,9 @@ protected final void doSetWebServiceTemplate(WebServiceTemplate template) { Assert.notNull(template, "'webServiceTemplate' must not be null"); this.webServiceTemplate = template; this.webServiceTemplateExplicitlySet = true; + if (!StringUtils.hasText(this.uri)) { + this.uri = this.webServiceTemplate.getDefaultUri(); + } } public void setMessageFactory(WebServiceMessageFactory messageFactory) { @@ -200,6 +209,10 @@ protected void doInit() { this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory()); Assert.state(this.destinationProvider == null || CollectionUtils.isEmpty(this.uriVariableExpressions), "uri variables are not supported when a DestinationProvider is supplied."); + + Assert.state(this.destinationProvider != null || StringUtils.hasText(this.uri), + "'destinationProvider' or 'uri' must be provided, " + + "or 'defaultUri' must be set on the 'WebServiceTemplate'"); } protected WebServiceTemplate getWebServiceTemplate() { @@ -234,7 +247,7 @@ protected WebServiceTemplate getWebServiceTemplate() { .withRoot(requestMessage) .build(); - Assert.notNull(this.uri, "'uri' must not be null"); + Assert.hasText(this.uri, "'uri' must be provided, or 'defaultUri' must be set on the 'WebServiceTemplate'"); return this.uriFactory.expand(this.uri, uriVariables); } diff --git a/spring-integration-ws/src/main/java/org/springframework/integration/ws/outbound/SimpleWebServiceOutboundGateway.java b/spring-integration-ws/src/main/java/org/springframework/integration/ws/outbound/SimpleWebServiceOutboundGateway.java index 188b0e0b61d..7870bd704fa 100644 --- a/spring-integration-ws/src/main/java/org/springframework/integration/ws/outbound/SimpleWebServiceOutboundGateway.java +++ b/spring-integration-ws/src/main/java/org/springframework/integration/ws/outbound/SimpleWebServiceOutboundGateway.java @@ -35,6 +35,7 @@ import org.springframework.ws.WebServiceMessageFactory; import org.springframework.ws.client.core.SourceExtractor; import org.springframework.ws.client.core.WebServiceMessageCallback; +import org.springframework.ws.client.core.WebServiceTemplate; import org.springframework.ws.client.support.destination.DestinationProvider; import org.springframework.ws.mime.Attachment; import org.springframework.ws.mime.MimeMessage; @@ -91,6 +92,28 @@ public SimpleWebServiceOutboundGateway(@Nullable String uri, @Nullable SourceExt this.sourceExtractor = (sourceExtractor != null) ? sourceExtractor : new DefaultSourceExtractor(); } + /** + * Create an instance based on the predefined {@link WebServiceTemplate} + * as an alternative to fine-grained configuration. + * @param template the {@link WebServiceTemplate} to use. + * @since 7.1 + */ + public SimpleWebServiceOutboundGateway(WebServiceTemplate template) { + this(template, null); + } + + /** + * Create an instance based on the predefined {@link WebServiceTemplate} + * as an alternative to fine-grained configuration. + * @param template the {@link WebServiceTemplate} to use. + * @param sourceExtractor the {@link SourceExtractor} to use. + * @since 7.1 + */ + public SimpleWebServiceOutboundGateway(WebServiceTemplate template, @Nullable SourceExtractor sourceExtractor) { + super(template); + this.sourceExtractor = (sourceExtractor != null) ? sourceExtractor : new DefaultSourceExtractor(); + } + /** * A flag to return the whole {@link WebServiceMessage} or build * {@code payload} based on {@link WebServiceMessage} diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests-context.xml b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests-context.xml index ec27ea443a9..a8987d17091 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests-context.xml +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests-context.xml @@ -23,11 +23,12 @@ - + + + new ClassPathXmlApplicationContext("invalidGatewayWithNeitherUriNorDestinationProvider.xml", - getClass())); + getClass())) + .withMessageContaining("'destinationProvider' or 'uri' must be provided, " + + "or 'defaultUri' must be set on the 'WebServiceTemplate'"); } public static class FooAdvice extends AbstractRequestHandlerAdvice { diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/invalidGatewayWithNeitherUriNorDestinationProvider.xml b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/invalidGatewayWithNeitherUriNorDestinationProvider.xml index 3644ef24203..e040035b833 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/invalidGatewayWithNeitherUriNorDestinationProvider.xml +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/invalidGatewayWithNeitherUriNorDestinationProvider.xml @@ -1,17 +1,14 @@ + https://www.springframework.org/schema/integration/ws/spring-integration-ws.xsd"> @@ -19,9 +16,6 @@ - - - + diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/dsl/WsDslTests.java b/spring-integration-ws/src/test/java/org/springframework/integration/ws/dsl/WsDslTests.java index e3422769d3c..dc09f4cf92c 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/dsl/WsDslTests.java +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/dsl/WsDslTests.java @@ -175,9 +175,10 @@ void marshallingOutboundTemplate() { WebServiceMessageCallback requestCallback = msg -> { }; Map uriVariableExpressions = new HashMap<>(); - uriVariableExpressions.put("foo", new LiteralExpression("bar")); - WebServiceTemplate template = mock(); - String uri = "foo"; + uriVariableExpressions.put("testVariable", new LiteralExpression("testValue")); + WebServiceTemplate template = new WebServiceTemplate(); + String uri = "testUri"; + template.setDefaultUri(uri); MarshallingWebServiceOutboundGateway gateway = Ws.marshallingOutboundGateway(template) .uri(uri) @@ -202,13 +203,12 @@ void simpleOutboundTemplate() { WebServiceMessageCallback requestCallback = msg -> { }; Map uriVariableExpressions = new HashMap<>(); - uriVariableExpressions.put("foo", new LiteralExpression("bar")); + uriVariableExpressions.put("testVariable", new LiteralExpression("testValue")); SourceExtractor sourceExtractor = mock(); - WebServiceTemplate template = mock(); - String uri = "foo"; + WebServiceTemplate template = new WebServiceTemplate(); + template.setDefaultUri("testUri"); SimpleWebServiceOutboundGateway gateway = Ws.simpleOutboundGateway(template) - .uri(uri) .sourceExtractor(sourceExtractor) .encodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY) .headerMapper(headerMapper) diff --git a/src/reference/antora/modules/ROOT/pages/whats-new.adoc b/src/reference/antora/modules/ROOT/pages/whats-new.adoc index b0d1e3f33c4..e46a1a28981 100644 --- a/src/reference/antora/modules/ROOT/pages/whats-new.adoc +++ b/src/reference/antora/modules/ROOT/pages/whats-new.adoc @@ -14,3 +14,8 @@ For more details, see the https://github.com/spring-projects/spring-integration/ In general, the project has been moved to the latest dependency versions. Java 17 is still the baseline, but Java 25 is supported. +[[x7.0-web-services-changes]] +=== Web Services Support Changes + +The Web Services Outbound Gateway now can rely on the provided `WebServiceTemplate.defaultUri`. +See xref:ws.adoc[] for more information. diff --git a/src/reference/antora/modules/ROOT/pages/ws.adoc b/src/reference/antora/modules/ROOT/pages/ws.adoc index a87166b2e62..e54b6227377 100644 --- a/src/reference/antora/modules/ROOT/pages/ws.adoc +++ b/src/reference/antora/modules/ROOT/pages/ws.adoc @@ -40,6 +40,7 @@ To invoke a web service when you send a message to a channel, you have two optio The former accepts either a `String` or `javax.xml.transform.Source` as the message payload. The latter supports any implementation of the `Marshaller` and `Unmarshaller` interfaces. Both require a Spring Web Services `DestinationProvider`, to determine the URI of the web service to be called. +Or explicit uri directly, or via `WebServiceTemplate.defaultUri` (since version 7.1). The following example shows both options for invoking a web service: [source,java]