From 6356e904fcba0abd28b1183e4f22a2255a7aa2c3 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Thu, 24 Oct 2024 00:04:57 -0700 Subject: [PATCH] Update web services support to use `ClientHttpRequestFactoryBuilder` Add a new `WebServiceMessageSenderFactory` factory interface to replace `HttpWebServiceMessageSenderBuilder`. The factory provides a general purpose way of creating `WebServiceMessageSender` instances, but most typically will be `ClientHttpRequestMessageSender` created from a `ClientHttpRequestFactoryBuilder`. A new `httpMessageSenderFactory` method has been added to the `WebServiceTemplateBuilder` class. This allows any sender to be plugged into the template. Closes gh-42886 --- .../MyWebServiceTemplateConfiguration.java | 15 +-- .../MyWebServiceTemplateConfiguration.kt | 13 +- .../HttpWebServiceMessageSenderBuilder.java | 62 ++++++--- .../WebServiceMessageSenderFactory.java | 74 +++++++++++ .../client/WebServiceTemplateBuilder.java | 111 ++++++++++------ ...derBuilderJettyClientIntegrationTests.java | 3 +- ...rBuilderReactorClientIntegrationTests.java | 1 + ...geSenderBuilderSimpleIntegrationTests.java | 1 + ...tpWebServiceMessageSenderBuilderTests.java | 3 +- .../WebServiceMessageSenderFactoryTests.java | 120 ++++++++++++++++++ .../WebServiceTemplateBuilderTests.java | 20 ++- 11 files changed, 342 insertions(+), 81 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/webservices/client/WebServiceMessageSenderFactory.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/WebServiceMessageSenderFactoryTests.java diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/webservices/template/MyWebServiceTemplateConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/webservices/template/MyWebServiceTemplateConfiguration.java index 29c6260aca..84c109abaf 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/webservices/template/MyWebServiceTemplateConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/webservices/template/MyWebServiceTemplateConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 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. @@ -18,7 +18,8 @@ package org.springframework.boot.docs.io.webservices.template; import java.time.Duration; -import org.springframework.boot.webservices.client.HttpWebServiceMessageSenderBuilder; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; +import org.springframework.boot.webservices.client.WebServiceMessageSenderFactory; import org.springframework.boot.webservices.client.WebServiceTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -30,13 +31,11 @@ public class MyWebServiceTemplateConfiguration { @Bean public WebServiceTemplate webServiceTemplate(WebServiceTemplateBuilder builder) { - // @formatter:off - WebServiceMessageSender sender = new HttpWebServiceMessageSenderBuilder() - .setConnectTimeout(Duration.ofSeconds(5)) - .setReadTimeout(Duration.ofSeconds(2)) - .build(); + ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults() + .withConnectTimeout(Duration.ofSeconds(2)) + .withReadTimeout(Duration.ofSeconds(2)); + WebServiceMessageSender sender = WebServiceMessageSenderFactory.http(settings).getWebServiceMessageSender(); return builder.messageSenders(sender).build(); - // @formatter:on } } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/webservices/template/MyWebServiceTemplateConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/webservices/template/MyWebServiceTemplateConfiguration.kt index c9a86da99d..b354d64ca1 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/webservices/template/MyWebServiceTemplateConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/webservices/template/MyWebServiceTemplateConfiguration.kt @@ -16,7 +16,8 @@ package org.springframework.boot.docs.io.webservices.template -import org.springframework.boot.webservices.client.HttpWebServiceMessageSenderBuilder +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings +import org.springframework.boot.webservices.client.WebServiceMessageSenderFactory import org.springframework.boot.webservices.client.WebServiceTemplateBuilder import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -28,11 +29,11 @@ class MyWebServiceTemplateConfiguration { @Bean fun webServiceTemplate(builder: WebServiceTemplateBuilder): WebServiceTemplate { - val sender = HttpWebServiceMessageSenderBuilder() - .setConnectTimeout(Duration.ofSeconds(5)) - .setReadTimeout(Duration.ofSeconds(2)) - .build() - return builder.messageSenders(sender).build() + val settings = ClientHttpRequestFactorySettings.defaults() + .withConnectTimeout(Duration.ofSeconds(2)) + .withReadTimeout(Duration.ofSeconds(2)); + val sender = WebServiceMessageSenderFactory.http(settings).getWebServiceMessageSender(); + return builder.messageSenders(sender).build(); } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilder.java index 5d25c93e60..114103c223 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -20,13 +20,13 @@ import java.time.Duration; import java.util.function.Function; import java.util.function.Supplier; +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; +import org.springframework.boot.http.client.JdkClientHttpRequestFactoryBuilder; import org.springframework.boot.ssl.SslBundle; -import org.springframework.boot.web.client.ClientHttpRequestFactories; -import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.util.Assert; import org.springframework.ws.transport.WebServiceMessageSender; -import org.springframework.ws.transport.http.ClientHttpRequestMessageSender; /** * {@link WebServiceMessageSender} builder that can detect a suitable HTTP library based @@ -34,16 +34,15 @@ import org.springframework.ws.transport.http.ClientHttpRequestMessageSender; * * @author Stephane Nicoll * @since 2.1.0 + * @deprecated since 3.4.0 in favor of + * {@link WebServiceMessageSenderFactory#http(ClientHttpRequestFactorySettings)} */ +@Deprecated(since = "3.4.0", forRemoval = true) public class HttpWebServiceMessageSenderBuilder { - private Duration connectTimeout; + private ClientHttpRequestFactoryBuilder requestFactoryBuilder; - private Duration readTimeout; - - private SslBundle sslBundle; - - private Function requestFactory; + private ClientHttpRequestFactorySettings requestFactorySettings = ClientHttpRequestFactorySettings.defaults(); /** * Set the connection timeout. @@ -51,7 +50,7 @@ public class HttpWebServiceMessageSenderBuilder { * @return a new builder instance */ public HttpWebServiceMessageSenderBuilder setConnectTimeout(Duration connectTimeout) { - this.connectTimeout = connectTimeout; + this.requestFactorySettings = this.requestFactorySettings.withConnectTimeout(connectTimeout); return this; } @@ -61,7 +60,7 @@ public class HttpWebServiceMessageSenderBuilder { * @return a new builder instance */ public HttpWebServiceMessageSenderBuilder setReadTimeout(Duration readTimeout) { - this.readTimeout = readTimeout; + this.requestFactorySettings = this.requestFactorySettings.withReadTimeout(readTimeout); return this; } @@ -71,7 +70,7 @@ public class HttpWebServiceMessageSenderBuilder { * @return a new builder instance */ public HttpWebServiceMessageSenderBuilder sslBundle(SslBundle sslBundle) { - this.sslBundle = sslBundle; + this.requestFactorySettings = this.requestFactorySettings.withSslBundle(sslBundle); return this; } @@ -84,7 +83,7 @@ public class HttpWebServiceMessageSenderBuilder { public HttpWebServiceMessageSenderBuilder requestFactory( Supplier requestFactorySupplier) { Assert.notNull(requestFactorySupplier, "RequestFactorySupplier must not be null"); - this.requestFactory = (settings) -> ClientHttpRequestFactories.get(requestFactorySupplier, settings); + this.requestFactoryBuilder = ClientHttpRequestFactoryBuilder.of(requestFactorySupplier); return this; } @@ -99,7 +98,21 @@ public class HttpWebServiceMessageSenderBuilder { public HttpWebServiceMessageSenderBuilder requestFactory( Function requestFactoryFunction) { Assert.notNull(requestFactoryFunction, "RequestFactoryFunction must not be null"); - this.requestFactory = requestFactoryFunction; + this.requestFactoryBuilder = requestFactoryFunction::apply; + return this; + } + + /** + * Set the {@link ClientHttpRequestFactoryBuilder} to use when creating the HTTP-based + * {@link WebServiceMessageSender}. + * @param requestFactoryBuilder the {@link ClientHttpRequestFactoryBuilder} to use + * @return this builder instance + * @since 3.4.0 + */ + public HttpWebServiceMessageSenderBuilder requestFactoryBuilder( + ClientHttpRequestFactoryBuilder requestFactoryBuilder) { + Assert.notNull(requestFactoryBuilder, "ClientHttpRequestFactoryBuilder must not be null"); + this.requestFactoryBuilder = requestFactoryBuilder; return this; } @@ -108,14 +121,21 @@ public class HttpWebServiceMessageSenderBuilder { * @return the {@link WebServiceMessageSender} instance */ public WebServiceMessageSender build() { - return new ClientHttpRequestMessageSender(getRequestFactory()); + ClientHttpRequestFactoryBuilder requestFactoryBuilder = getOrDetectRequestFactoryBuilder(); + return WebServiceMessageSenderFactory.http(requestFactoryBuilder, this.requestFactorySettings) + .getWebServiceMessageSender(); } - private ClientHttpRequestFactory getRequestFactory() { - ClientHttpRequestFactorySettings settings = new ClientHttpRequestFactorySettings(this.connectTimeout, - this.readTimeout, this.sslBundle); - return (this.requestFactory != null) ? this.requestFactory.apply(settings) - : ClientHttpRequestFactories.get(settings); + private ClientHttpRequestFactoryBuilder getOrDetectRequestFactoryBuilder() { + if (this.requestFactoryBuilder != null) { + return this.requestFactoryBuilder; + } + ClientHttpRequestFactoryBuilder builder = ClientHttpRequestFactoryBuilder.detect(); + if (builder instanceof JdkClientHttpRequestFactoryBuilder) { + // Same logic as earlier versions which did not support JDK client factories + return ClientHttpRequestFactoryBuilder.simple(); + } + return builder; } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/webservices/client/WebServiceMessageSenderFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/webservices/client/WebServiceMessageSenderFactory.java new file mode 100644 index 0000000000..add5b44177 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/webservices/client/WebServiceMessageSenderFactory.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2024 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 + * + * https://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.boot.webservices.client; + +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.util.Assert; +import org.springframework.ws.transport.WebServiceMessageSender; +import org.springframework.ws.transport.http.ClientHttpRequestMessageSender; + +/** + * Factory that can be used to create a {@link WebServiceMessageSender}. + * + * @author Phillip Webb + * @since 3.4.0 + */ +@FunctionalInterface +public interface WebServiceMessageSenderFactory { + + /** + * Return a new {@link WebServiceMessageSender} instance. + * @return the web service message sender + */ + WebServiceMessageSender getWebServiceMessageSender(); + + /** + * Returns a factory that will create a {@link ClientHttpRequestMessageSender} backed + * by a detected {@link ClientHttpRequestFactory}. + * @return a new {@link WebServiceMessageSenderFactory} + */ + static WebServiceMessageSenderFactory http() { + return http(ClientHttpRequestFactoryBuilder.detect(), null); + } + + /** + * Returns a factory that will create a {@link ClientHttpRequestMessageSender} backed + * by a detected {@link ClientHttpRequestFactory}. + * @param requestFactorySettings the setting to apply + * @return a new {@link WebServiceMessageSenderFactory} + */ + static WebServiceMessageSenderFactory http(ClientHttpRequestFactorySettings requestFactorySettings) { + return http(ClientHttpRequestFactoryBuilder.detect(), requestFactorySettings); + } + + /** + * Returns a factory that will create a {@link ClientHttpRequestMessageSender} backed + * by a {@link ClientHttpRequestFactory} created from the given + * {@link ClientHttpRequestFactoryBuilder}. + * @param requestFactoryBuilder the request factory builder to use + * @param requestFactorySettings the settings to apply + * @return a new {@link WebServiceMessageSenderFactory} + */ + static WebServiceMessageSenderFactory http(ClientHttpRequestFactoryBuilder requestFactoryBuilder, + ClientHttpRequestFactorySettings requestFactorySettings) { + Assert.notNull(requestFactoryBuilder, "'requestFactoryBuilder' must not be null"); + return () -> new ClientHttpRequestMessageSender(requestFactoryBuilder.build(requestFactorySettings)); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/webservices/client/WebServiceTemplateBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/webservices/client/WebServiceTemplateBuilder.java index 9f2c380624..ebdbc1fba3 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/webservices/client/WebServiceTemplateBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/webservices/client/WebServiceTemplateBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -56,6 +56,8 @@ import org.springframework.ws.transport.WebServiceMessageSender; */ public class WebServiceTemplateBuilder { + private final WebServiceMessageSenderFactory httpMessageSenderFactory; + private final boolean detectHttpMessageSender; private final Set interceptors; @@ -77,6 +79,7 @@ public class WebServiceTemplateBuilder { private final WebServiceMessageFactory messageFactory; public WebServiceTemplateBuilder(WebServiceTemplateCustomizer... customizers) { + this.httpMessageSenderFactory = null; this.detectHttpMessageSender = true; this.interceptors = null; this.internalCustomizers = null; @@ -89,11 +92,13 @@ public class WebServiceTemplateBuilder { this.messageFactory = null; } - private WebServiceTemplateBuilder(boolean detectHttpMessageSender, Set interceptors, + private WebServiceTemplateBuilder(WebServiceMessageSenderFactory messageSenderFactory, + boolean detectHttpMessageSender, Set interceptors, Set internalCustomizers, Set customizers, WebServiceMessageSenders messageSenders, Marshaller marshaller, Unmarshaller unmarshaller, DestinationProvider destinationProvider, Class transformerFactoryClass, WebServiceMessageFactory messageFactory) { + this.httpMessageSenderFactory = messageSenderFactory; this.detectHttpMessageSender = detectHttpMessageSender; this.interceptors = interceptors; this.internalCustomizers = internalCustomizers; @@ -106,18 +111,33 @@ public class WebServiceTemplateBuilder { this.messageFactory = messageFactory; } + /** + * Set the {@link WebServiceMessageSenderFactory} that should be used to send HTTP + * messages. + * @param messageSenderFactory the {@link WebServiceMessageSenderFactory} to use + * @return a new builder instance + * @since 3.4.0 + * @see HttpWebServiceMessageSenderBuilder + */ + public WebServiceTemplateBuilder httpMessageSenderFactory(WebServiceMessageSenderFactory messageSenderFactory) { + Assert.notNull(this.messageSenders, "HttpWebServiceMessageSenderBuilder must not be null"); + return new WebServiceTemplateBuilder(messageSenderFactory, this.detectHttpMessageSender, this.interceptors, + this.internalCustomizers, this.customizers, this.messageSenders, this.marshaller, this.unmarshaller, + this.destinationProvider, this.transformerFactoryClass, this.messageFactory); + } + /** * Set if a suitable HTTP-based {@link WebServiceMessageSender} should be detected - * based on the classpath. Default is {@code true}. + * based on the classpath when one has not been specified. Default is {@code true}. * @param detectHttpMessageSender if an HTTP-based {@link WebServiceMessageSender} * should be detected * @return a new builder instance * @see HttpWebServiceMessageSenderBuilder */ public WebServiceTemplateBuilder detectHttpMessageSender(boolean detectHttpMessageSender) { - return new WebServiceTemplateBuilder(detectHttpMessageSender, this.interceptors, this.internalCustomizers, - this.customizers, this.messageSenders, this.marshaller, this.unmarshaller, this.destinationProvider, - this.transformerFactoryClass, this.messageFactory); + return new WebServiceTemplateBuilder(this.httpMessageSenderFactory, detectHttpMessageSender, this.interceptors, + this.internalCustomizers, this.customizers, this.messageSenders, this.marshaller, this.unmarshaller, + this.destinationProvider, this.transformerFactoryClass, this.messageFactory); } /** @@ -149,9 +169,10 @@ public class WebServiceTemplateBuilder { */ public WebServiceTemplateBuilder messageSenders(Collection messageSenders) { Assert.notNull(messageSenders, "MessageSenders must not be null"); - return new WebServiceTemplateBuilder(this.detectHttpMessageSender, this.interceptors, this.internalCustomizers, - this.customizers, this.messageSenders.set(messageSenders), this.marshaller, this.unmarshaller, - this.destinationProvider, this.transformerFactoryClass, this.messageFactory); + return new WebServiceTemplateBuilder(this.httpMessageSenderFactory, this.detectHttpMessageSender, + this.interceptors, this.internalCustomizers, this.customizers, this.messageSenders.set(messageSenders), + this.marshaller, this.unmarshaller, this.destinationProvider, this.transformerFactoryClass, + this.messageFactory); } /** @@ -176,9 +197,10 @@ public class WebServiceTemplateBuilder { public WebServiceTemplateBuilder additionalMessageSenders( Collection messageSenders) { Assert.notNull(messageSenders, "MessageSenders must not be null"); - return new WebServiceTemplateBuilder(this.detectHttpMessageSender, this.interceptors, this.internalCustomizers, - this.customizers, this.messageSenders.add(messageSenders), this.marshaller, this.unmarshaller, - this.destinationProvider, this.transformerFactoryClass, this.messageFactory); + return new WebServiceTemplateBuilder(this.httpMessageSenderFactory, this.detectHttpMessageSender, + this.interceptors, this.internalCustomizers, this.customizers, this.messageSenders.add(messageSenders), + this.marshaller, this.unmarshaller, this.destinationProvider, this.transformerFactoryClass, + this.messageFactory); } /** @@ -204,7 +226,7 @@ public class WebServiceTemplateBuilder { */ public WebServiceTemplateBuilder interceptors(Collection interceptors) { Assert.notNull(interceptors, "Interceptors must not be null"); - return new WebServiceTemplateBuilder(this.detectHttpMessageSender, + return new WebServiceTemplateBuilder(this.httpMessageSenderFactory, this.detectHttpMessageSender, append(Collections.emptySet(), interceptors), this.internalCustomizers, this.customizers, this.messageSenders, this.marshaller, this.unmarshaller, this.destinationProvider, this.transformerFactoryClass, this.messageFactory); @@ -231,9 +253,10 @@ public class WebServiceTemplateBuilder { */ public WebServiceTemplateBuilder additionalInterceptors(Collection interceptors) { Assert.notNull(interceptors, "Interceptors must not be null"); - return new WebServiceTemplateBuilder(this.detectHttpMessageSender, append(this.interceptors, interceptors), - this.internalCustomizers, this.customizers, this.messageSenders, this.marshaller, this.unmarshaller, - this.destinationProvider, this.transformerFactoryClass, this.messageFactory); + return new WebServiceTemplateBuilder(this.httpMessageSenderFactory, this.detectHttpMessageSender, + append(this.interceptors, interceptors), this.internalCustomizers, this.customizers, + this.messageSenders, this.marshaller, this.unmarshaller, this.destinationProvider, + this.transformerFactoryClass, this.messageFactory); } /** @@ -261,7 +284,8 @@ public class WebServiceTemplateBuilder { */ public WebServiceTemplateBuilder customizers(Collection customizers) { Assert.notNull(customizers, "Customizers must not be null"); - return new WebServiceTemplateBuilder(this.detectHttpMessageSender, this.interceptors, this.internalCustomizers, + return new WebServiceTemplateBuilder(this.httpMessageSenderFactory, this.detectHttpMessageSender, + this.interceptors, this.internalCustomizers, append(Collections.emptySet(), customizers), this.messageSenders, this.marshaller, this.unmarshaller, this.destinationProvider, this.transformerFactoryClass, this.messageFactory); @@ -291,9 +315,10 @@ public class WebServiceTemplateBuilder { public WebServiceTemplateBuilder additionalCustomizers( Collection customizers) { Assert.notNull(customizers, "Customizers must not be null"); - return new WebServiceTemplateBuilder(this.detectHttpMessageSender, this.interceptors, this.internalCustomizers, - append(this.customizers, customizers), this.messageSenders, this.marshaller, this.unmarshaller, - this.destinationProvider, this.transformerFactoryClass, this.messageFactory); + return new WebServiceTemplateBuilder(this.httpMessageSenderFactory, this.detectHttpMessageSender, + this.interceptors, this.internalCustomizers, append(this.customizers, customizers), this.messageSenders, + this.marshaller, this.unmarshaller, this.destinationProvider, this.transformerFactoryClass, + this.messageFactory); } /** @@ -304,7 +329,8 @@ public class WebServiceTemplateBuilder { * @see WebServiceTemplate#setCheckConnectionForFault(boolean) */ public WebServiceTemplateBuilder setCheckConnectionForFault(boolean checkConnectionForFault) { - return new WebServiceTemplateBuilder(this.detectHttpMessageSender, this.interceptors, + return new WebServiceTemplateBuilder(this.httpMessageSenderFactory, this.detectHttpMessageSender, + this.interceptors, append(this.internalCustomizers, new CheckConnectionFaultCustomizer(checkConnectionForFault)), this.customizers, this.messageSenders, this.marshaller, this.unmarshaller, this.destinationProvider, this.transformerFactoryClass, this.messageFactory); @@ -318,7 +344,8 @@ public class WebServiceTemplateBuilder { * @see WebServiceTemplate#setCheckConnectionForError(boolean) */ public WebServiceTemplateBuilder setCheckConnectionForError(boolean checkConnectionForError) { - return new WebServiceTemplateBuilder(this.detectHttpMessageSender, this.interceptors, + return new WebServiceTemplateBuilder(this.httpMessageSenderFactory, this.detectHttpMessageSender, + this.interceptors, append(this.internalCustomizers, new CheckConnectionForErrorCustomizer(checkConnectionForError)), this.customizers, this.messageSenders, this.marshaller, this.unmarshaller, this.destinationProvider, this.transformerFactoryClass, this.messageFactory); @@ -332,9 +359,9 @@ public class WebServiceTemplateBuilder { */ public WebServiceTemplateBuilder setWebServiceMessageFactory(WebServiceMessageFactory messageFactory) { Assert.notNull(messageFactory, "MessageFactory must not be null"); - return new WebServiceTemplateBuilder(this.detectHttpMessageSender, this.interceptors, this.internalCustomizers, - this.customizers, this.messageSenders, this.marshaller, this.unmarshaller, this.destinationProvider, - this.transformerFactoryClass, messageFactory); + return new WebServiceTemplateBuilder(this.httpMessageSenderFactory, this.detectHttpMessageSender, + this.interceptors, this.internalCustomizers, this.customizers, this.messageSenders, this.marshaller, + this.unmarshaller, this.destinationProvider, this.transformerFactoryClass, messageFactory); } /** @@ -344,9 +371,9 @@ public class WebServiceTemplateBuilder { * @see WebServiceTemplate#setUnmarshaller(Unmarshaller) */ public WebServiceTemplateBuilder setUnmarshaller(Unmarshaller unmarshaller) { - return new WebServiceTemplateBuilder(this.detectHttpMessageSender, this.interceptors, this.internalCustomizers, - this.customizers, this.messageSenders, this.marshaller, unmarshaller, this.destinationProvider, - this.transformerFactoryClass, this.messageFactory); + return new WebServiceTemplateBuilder(this.httpMessageSenderFactory, this.detectHttpMessageSender, + this.interceptors, this.internalCustomizers, this.customizers, this.messageSenders, this.marshaller, + unmarshaller, this.destinationProvider, this.transformerFactoryClass, this.messageFactory); } /** @@ -356,9 +383,9 @@ public class WebServiceTemplateBuilder { * @see WebServiceTemplate#setMarshaller(Marshaller) */ public WebServiceTemplateBuilder setMarshaller(Marshaller marshaller) { - return new WebServiceTemplateBuilder(this.detectHttpMessageSender, this.interceptors, this.internalCustomizers, - this.customizers, this.messageSenders, marshaller, this.unmarshaller, this.destinationProvider, - this.transformerFactoryClass, this.messageFactory); + return new WebServiceTemplateBuilder(this.httpMessageSenderFactory, this.detectHttpMessageSender, + this.interceptors, this.internalCustomizers, this.customizers, this.messageSenders, marshaller, + this.unmarshaller, this.destinationProvider, this.transformerFactoryClass, this.messageFactory); } /** @@ -368,7 +395,8 @@ public class WebServiceTemplateBuilder { * @see WebServiceTemplate#setFaultMessageResolver(FaultMessageResolver) */ public WebServiceTemplateBuilder setFaultMessageResolver(FaultMessageResolver faultMessageResolver) { - return new WebServiceTemplateBuilder(this.detectHttpMessageSender, this.interceptors, + return new WebServiceTemplateBuilder(this.httpMessageSenderFactory, this.detectHttpMessageSender, + this.interceptors, append(this.internalCustomizers, new FaultMessageResolverCustomizer(faultMessageResolver)), this.customizers, this.messageSenders, this.marshaller, this.unmarshaller, this.destinationProvider, this.transformerFactoryClass, this.messageFactory); @@ -382,9 +410,9 @@ public class WebServiceTemplateBuilder { */ public WebServiceTemplateBuilder setTransformerFactoryClass( Class transformerFactoryClass) { - return new WebServiceTemplateBuilder(this.detectHttpMessageSender, this.interceptors, this.internalCustomizers, - this.customizers, this.messageSenders, this.marshaller, this.unmarshaller, this.destinationProvider, - transformerFactoryClass, this.messageFactory); + return new WebServiceTemplateBuilder(this.httpMessageSenderFactory, this.detectHttpMessageSender, + this.interceptors, this.internalCustomizers, this.customizers, this.messageSenders, this.marshaller, + this.unmarshaller, this.destinationProvider, transformerFactoryClass, this.messageFactory); } /** @@ -411,9 +439,9 @@ public class WebServiceTemplateBuilder { */ public WebServiceTemplateBuilder setDestinationProvider(DestinationProvider destinationProvider) { Assert.notNull(destinationProvider, "DestinationProvider must not be null"); - return new WebServiceTemplateBuilder(this.detectHttpMessageSender, this.interceptors, this.internalCustomizers, - this.customizers, this.messageSenders, this.marshaller, this.unmarshaller, destinationProvider, - this.transformerFactoryClass, this.messageFactory); + return new WebServiceTemplateBuilder(this.httpMessageSenderFactory, this.detectHttpMessageSender, + this.interceptors, this.internalCustomizers, this.customizers, this.messageSenders, this.marshaller, + this.unmarshaller, destinationProvider, this.transformerFactoryClass, this.messageFactory); } /** @@ -480,9 +508,12 @@ public class WebServiceTemplateBuilder { } private void configureMessageSenders(T webServiceTemplate) { - if (this.messageSenders.isOnlyAdditional() && this.detectHttpMessageSender) { + if (this.messageSenders.isOnlyAdditional() + && (this.httpMessageSenderFactory != null || this.detectHttpMessageSender)) { + WebServiceMessageSenderFactory httpMessageSenderFactory = (this.httpMessageSenderFactory != null) + ? this.httpMessageSenderFactory : WebServiceMessageSenderFactory.http(); Set merged = append(this.messageSenders.getMessageSenders(), - new HttpWebServiceMessageSenderBuilder().build()); + httpMessageSenderFactory.getWebServiceMessageSender()); webServiceTemplate.setMessageSenders(merged.toArray(new WebServiceMessageSender[0])); } else if (!CollectionUtils.isEmpty(this.messageSenders.getMessageSenders())) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderJettyClientIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderJettyClientIntegrationTests.java index ce64fddef1..c9a23e33d7 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderJettyClientIntegrationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderJettyClientIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -37,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Stephane Nicoll */ @ClassPathExclusions("httpclient5-*.jar") +@SuppressWarnings("removal") class HttpWebServiceMessageSenderBuilderJettyClientIntegrationTests { private final HttpWebServiceMessageSenderBuilder builder = new HttpWebServiceMessageSenderBuilder(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderReactorClientIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderReactorClientIntegrationTests.java index b315d8d23e..846e01a76f 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderReactorClientIntegrationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderReactorClientIntegrationTests.java @@ -38,6 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Andy Wilkinson */ @ClassPathExclusions({ "httpclient5-*.jar", "jetty-client-*.jar" }) +@SuppressWarnings("removal") class HttpWebServiceMessageSenderBuilderReactorClientIntegrationTests { private final HttpWebServiceMessageSenderBuilder builder = new HttpWebServiceMessageSenderBuilder(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java index ce56dab57f..74f9150466 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderSimpleIntegrationTests.java @@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Stephane Nicoll */ @ClassPathExclusions({ "httpclient5-*.jar", "jetty-client-*.jar", "reactor-netty-http-*.jar" }) +@SuppressWarnings("removal") class HttpWebServiceMessageSenderBuilderSimpleIntegrationTests { private final HttpWebServiceMessageSenderBuilder builder = new HttpWebServiceMessageSenderBuilder(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderTests.java index 2b0b312590..ff36d63547 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/HttpWebServiceMessageSenderBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -34,6 +34,7 @@ import static org.mockito.Mockito.mock; * * @author Stephane Nicoll */ +@SuppressWarnings("removal") class HttpWebServiceMessageSenderBuilderTests { @Test diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/WebServiceMessageSenderFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/WebServiceMessageSenderFactoryTests.java new file mode 100644 index 0000000000..2cb329dccb --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/WebServiceMessageSenderFactoryTests.java @@ -0,0 +1,120 @@ +/* + * Copyright 2012-2024 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 + * + * https://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.boot.webservices.client; + +import java.time.Duration; + +import org.eclipse.jetty.client.HttpClient; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.client.JdkClientHttpRequestFactory; +import org.springframework.http.client.JettyClientHttpRequestFactory; +import org.springframework.http.client.ReactorClientHttpRequestFactory; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.ws.transport.WebServiceMessageSender; +import org.springframework.ws.transport.http.ClientHttpRequestMessageSender; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link WebServiceMessageSenderFactory}. + * + * @author Phillip Webb + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +class WebServiceMessageSenderFactoryTests { + + @Test + void httpWhenDetectedHttpComponents() { + WebServiceMessageSender sender = WebServiceMessageSenderFactory.http().getWebServiceMessageSender(); + assertRequestFactoryInstanceOf(sender, HttpComponentsClientHttpRequestFactory.class); + } + + @Test + @ClassPathExclusions("httpclient5-*.jar") + void httpWhenDetectedJetty() { + WebServiceMessageSender sender = WebServiceMessageSenderFactory.http().getWebServiceMessageSender(); + assertRequestFactoryInstanceOf(sender, JettyClientHttpRequestFactory.class); + } + + @Test + @ClassPathExclusions({ "httpclient5-*.jar", "jetty-client-*.jar" }) + void httpWhenDetectedReactor() { + WebServiceMessageSender sender = WebServiceMessageSenderFactory.http().getWebServiceMessageSender(); + assertRequestFactoryInstanceOf(sender, ReactorClientHttpRequestFactory.class); + } + + @Test + @ClassPathExclusions({ "httpclient5-*.jar", "jetty-client-*.jar", "reactor-netty-http-*.jar" }) + void httpWhenDetectedJdk() { + WebServiceMessageSender sender = WebServiceMessageSenderFactory.http().getWebServiceMessageSender(); + assertRequestFactoryInstanceOf(sender, JdkClientHttpRequestFactory.class); + } + + @Test + @ClassPathExclusions("httpclient5-*.jar") + void httpWithSettingsUsesSettings() { + ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults() + .withConnectTimeout(Duration.ofSeconds(5)) + .withReadTimeout(Duration.ofSeconds(2)); + WebServiceMessageSender sender = WebServiceMessageSenderFactory.http(settings).getWebServiceMessageSender(); + assertTimeoutsOnJetty(sender); + } + + @Test + void httpWithFactoryAndSettingsUsesFactoryAndSettings() { + ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults() + .withConnectTimeout(Duration.ofSeconds(5)) + .withReadTimeout(Duration.ofSeconds(2)); + WebServiceMessageSender sender = WebServiceMessageSenderFactory + .http(ClientHttpRequestFactoryBuilder.jetty(), settings) + .getWebServiceMessageSender(); + assertTimeoutsOnJetty(sender); + } + + @Test + void httpWithFactoryAndSettingsWhenFactoryIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> WebServiceMessageSenderFactory.http(null, null)) + .withMessage("'requestFactoryBuilder' must not be null"); + } + + private void assertTimeoutsOnJetty(WebServiceMessageSender sender) { + ClientHttpRequestFactory requestFactory = getRequestFactory(sender); + HttpClient client = (HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient"); + assertThat(client).isNotNull(); + assertThat(client.getConnectTimeout()).isEqualTo(5000); + assertThat(requestFactory).hasFieldOrPropertyWithValue("readTimeout", 2000L); + } + + private void assertRequestFactoryInstanceOf(WebServiceMessageSender sender, Class expectedRequestFactoryType) { + assertThat(getRequestFactory(sender)).isInstanceOf(expectedRequestFactoryType); + } + + private ClientHttpRequestFactory getRequestFactory(WebServiceMessageSender sender) { + assertThat(sender).isInstanceOf(ClientHttpRequestMessageSender.class); + ClientHttpRequestFactory requestFactory = ((ClientHttpRequestMessageSender) sender).getRequestFactory(); + return requestFactory; + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/WebServiceTemplateBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/WebServiceTemplateBuilderTests.java index f6987ceb00..021708d5d3 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/WebServiceTemplateBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/webservices/client/WebServiceTemplateBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -30,6 +30,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.client.JdkClientHttpRequestFactory; import org.springframework.oxm.Marshaller; import org.springframework.oxm.Unmarshaller; import org.springframework.ws.WebServiceMessageFactory; @@ -53,6 +54,7 @@ import static org.mockito.Mockito.spy; * * @author Stephane Nicoll * @author Dmytro Nosan + * @author Phillip Webb */ @ExtendWith(MockitoExtension.class) class WebServiceTemplateBuilderTests { @@ -77,7 +79,7 @@ class WebServiceTemplateBuilderTests { WebServiceTemplate webServiceTemplate = this.builder.build(); assertThat(webServiceTemplate.getMessageSenders()).hasSize(1); WebServiceMessageSender messageSender = webServiceTemplate.getMessageSenders()[0]; - assertHttpComponentsRequestFactory(messageSender); + assertRequestFactoryInstanceOf(messageSender, HttpComponentsClientHttpRequestFactory.class); } @Test @@ -87,6 +89,16 @@ class WebServiceTemplateBuilderTests { assertThat(webServiceTemplate.getMessageSenders()[0]).isInstanceOf(HttpUrlConnectionMessageSender.class); } + @Test + void httpMessageSenderFactoryUsesFactory() { + WebServiceTemplate webServiceTemplate = this.builder + .httpMessageSenderFactory(() -> new ClientHttpRequestMessageSender(new JdkClientHttpRequestFactory())) + .build(); + assertThat(webServiceTemplate.getMessageSenders()).hasSize(1); + WebServiceMessageSender messageSender = webServiceTemplate.getMessageSenders()[0]; + assertRequestFactoryInstanceOf(messageSender, JdkClientHttpRequestFactory.class); + } + @Test void messageSendersWhenSendersAreAreNullShouldThrowException() { assertThatIllegalArgumentException() @@ -334,11 +346,11 @@ class WebServiceTemplateBuilderTests { assertThat(webServiceTemplate.getDestinationProvider()).isEqualTo(destinationProvider); } - private void assertHttpComponentsRequestFactory(WebServiceMessageSender messageSender) { + private void assertRequestFactoryInstanceOf(WebServiceMessageSender messageSender, Class type) { assertThat(messageSender).isInstanceOf(ClientHttpRequestMessageSender.class); ClientHttpRequestMessageSender sender = (ClientHttpRequestMessageSender) messageSender; ClientHttpRequestFactory requestFactory = sender.getRequestFactory(); - assertThat(requestFactory).isInstanceOf(HttpComponentsClientHttpRequestFactory.class); + assertThat(requestFactory).isInstanceOf(type); } }