diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientWithRestTemplateBuilderTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientWithRestTemplateBuilderTests.java new file mode 100644 index 00000000000..6a216e114e8 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientWithRestTemplateBuilderTests.java @@ -0,0 +1,68 @@ +/* + * 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.test.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.Builder; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for building a {@link RestClient} from a {@link RestTemplateBuilder}. + * + * @author Scott Frederick + */ +class RestClientWithRestTemplateBuilderTests { + + @Test + void buildUsingRestTemplateBuilderRootUri() { + RestTemplate restTemplate = new RestTemplateBuilder().rootUri("https://resttemplate.example.com").build(); + RestClient.Builder builder = RestClient.builder(restTemplate); + RestClient client = buildMockedClient(builder, "https://resttemplate.example.com/test"); + assertThat(client.get().uri("/test").retrieve().toBodilessEntity().getStatusCode().is2xxSuccessful()).isTrue(); + } + + @Test + void buildUsingRestClientBuilderBaseUrl() { + RestTemplate restTemplate = new RestTemplateBuilder().build(); + RestClient.Builder builder = RestClient.builder(restTemplate).baseUrl("https://restclient.example.com"); + RestClient client = buildMockedClient(builder, "https://restclient.example.com/test"); + assertThat(client.get().uri("/test").retrieve().toBodilessEntity().getStatusCode().is2xxSuccessful()).isTrue(); + } + + @Test + void buildRestTemplateBuilderRootUriAndRestClientBuilderBaseUrl() { + RestTemplate restTemplate = new RestTemplateBuilder().rootUri("https://resttemplate.example.com").build(); + RestClient.Builder builder = RestClient.builder(restTemplate).baseUrl("https://restclient.example.com"); + RestClient client = buildMockedClient(builder, "https://resttemplate.example.com/test"); + assertThat(client.get().uri("/test").retrieve().toBodilessEntity().getStatusCode().is2xxSuccessful()).isTrue(); + } + + private RestClient buildMockedClient(Builder builder, String url) { + MockRestServiceServer server = MockRestServiceServer.bindTo(builder).build(); + server.expect(requestTo(url)).andRespond(withSuccess()); + return builder.build(); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientWithRestTemplateTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientWithRestTemplateTests.java new file mode 100644 index 00000000000..556991d9347 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/client/RestClientWithRestTemplateTests.java @@ -0,0 +1,72 @@ +/* + * 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.test.autoconfigure.web.client; + +import org.junit.jupiter.api.Test; + +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.Builder; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.DefaultUriBuilderFactory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for building a {@link RestClient} from a {@link RestTemplate}. + * + * @author Scott Frederick + */ +class RestClientWithRestTemplateTests { + + @Test + void buildUsingRestTemplateUriTemplateHandler() { + RestTemplate restTemplate = new RestTemplate(); + DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("https://resttemplate.example.com"); + restTemplate.setUriTemplateHandler(uriBuilderFactory); + Builder builder = RestClient.builder(restTemplate); + RestClient client = buildMockedClient(builder, "https://resttemplate.example.com/test"); + assertThat(client.get().uri("/test").retrieve().toBodilessEntity().getStatusCode().is2xxSuccessful()).isTrue(); + } + + @Test + void buildUsingRestClientBuilderBaseUrl() { + RestTemplate restTemplate = new RestTemplate(); + Builder builder = RestClient.builder(restTemplate).baseUrl("https://restclient.example.com"); + RestClient client = buildMockedClient(builder, "https://restclient.example.com/test"); + assertThat(client.get().uri("/test").retrieve().toBodilessEntity().getStatusCode().is2xxSuccessful()).isTrue(); + } + + @Test + void buildUsingRestTemplateUriTemplateHandlerAndRestClientBuilderBaseUrl() { + RestTemplate restTemplate = new RestTemplate(); + DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("https://resttemplate.example.com"); + restTemplate.setUriTemplateHandler(uriBuilderFactory); + Builder builder = RestClient.builder(restTemplate).baseUrl("https://restclient.example.com"); + RestClient client = buildMockedClient(builder, "https://resttemplate.example.com/test"); + assertThat(client.get().uri("/test").retrieve().toBodilessEntity().getStatusCode().is2xxSuccessful()).isTrue(); + } + + private RestClient buildMockedClient(Builder builder, String url) { + MockRestServiceServer server = MockRestServiceServer.bindTo(builder).build(); + server.expect(requestTo(url)).andRespond(withSuccess()); + return builder.build(); + } + +} diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java index 901b999bbf3..5f44bac53b4 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java @@ -171,8 +171,8 @@ public class TestRestTemplate { } /** - * Returns the root URI applied by a {@link RootUriTemplateHandler} or {@code ""} if - * the root URI is not available. + * Returns the root URI applied by {@link RestTemplateBuilder#rootUri(String)} or + * {@code ""} if the root URI has not been applied. * @return the root URI */ public String getRootUri() { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java index a10a9ce1002..49d8ac59f60 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.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. @@ -150,10 +150,8 @@ public class RestTemplateBuilder { /** * Set a root URL that should be applied to each request that starts with {@code '/'}. - * Since this works by adding a {@link UriTemplateHandler} to the - * {@link RestTemplate}, the root URL will only apply when {@code String} variants of - * the {@link RestTemplate} methods are used for specifying the request URL. See - * {@link RootUriTemplateHandler} for details. + * The root URL will only apply when {@code String} variants of the + * {@link RestTemplate} methods are used for specifying the request URL. * @param rootUri the root URI or {@code null} * @return a new builder instance */ @@ -639,7 +637,7 @@ public class RestTemplateBuilder { restTemplate.setErrorHandler(this.errorHandler); } if (this.rootUri != null) { - RootUriTemplateHandler.addTo(restTemplate, this.rootUri); + RootUriBuilderFactory.applyTo(restTemplate, this.rootUri); } restTemplate.getInterceptors().addAll(this.interceptors); if (!CollectionUtils.isEmpty(this.customizers)) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriBuilderFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriBuilderFactory.java new file mode 100644 index 00000000000..da2945a7f47 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriBuilderFactory.java @@ -0,0 +1,65 @@ +/* + * 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.web.client; + +import org.springframework.util.Assert; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriBuilder; +import org.springframework.web.util.UriBuilderFactory; +import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.web.util.UriTemplateHandler; + +/** + * {@link UriBuilderFactory} to set the root for URI that starts with {@code '/'}. + * + * @author Scott Frederick + * @since 3.2.3 + */ +public class RootUriBuilderFactory extends RootUriTemplateHandler implements UriBuilderFactory { + + @SuppressWarnings("removal") + RootUriBuilderFactory(String rootUri) { + super(rootUri); + } + + @SuppressWarnings("removal") + RootUriBuilderFactory(String rootUri, UriTemplateHandler delegate) { + super(rootUri, delegate); + } + + @Override + public UriBuilder uriString(String uriTemplate) { + return UriComponentsBuilder.fromUriString(apply(uriTemplate)); + } + + @Override + public UriBuilder builder() { + return UriComponentsBuilder.newInstance(); + } + + /** + * Apply a {@link RootUriBuilderFactory} instance to the given {@link RestTemplate}. + * @param restTemplate the {@link RestTemplate} to add the builder factory to + * @param rootUri the root URI + */ + static void applyTo(RestTemplate restTemplate, String rootUri) { + Assert.notNull(restTemplate, "RestTemplate must not be null"); + RootUriBuilderFactory handler = new RootUriBuilderFactory(rootUri, restTemplate.getUriTemplateHandler()); + restTemplate.setUriTemplateHandler(handler); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java index e52c59b95fe..4a007a6fcaa 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.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. @@ -30,6 +30,7 @@ import org.springframework.web.util.UriTemplateHandler; * {@link UriTemplateHandler} to set the root for URI that starts with {@code '/'}. * * @author Phillip Webb + * @author Scott Frederick * @since 1.4.0 */ public class RootUriTemplateHandler implements UriTemplateHandler { @@ -47,7 +48,9 @@ public class RootUriTemplateHandler implements UriTemplateHandler { /** * Create a new {@link RootUriTemplateHandler} instance. * @param rootUri the root URI to be used to prefix relative URLs + * @deprecated since 3.2.3 for removal in 3.4.0, with no replacement */ + @Deprecated(since = "3.2.3", forRemoval = true) public RootUriTemplateHandler(String rootUri) { this(rootUri, new DefaultUriBuilderFactory()); } @@ -55,8 +58,10 @@ public class RootUriTemplateHandler implements UriTemplateHandler { /** * Create a new {@link RootUriTemplateHandler} instance. * @param rootUri the root URI to be used to prefix relative URLs - * @param handler the delegate handler + * @param handler the handler handler + * @deprecated since 3.2.3 for removal in 3.4.0, with no replacement */ + @Deprecated(since = "3.2.3", forRemoval = true) public RootUriTemplateHandler(String rootUri, UriTemplateHandler handler) { Assert.notNull(rootUri, "RootUri must not be null"); Assert.notNull(handler, "Handler must not be null"); @@ -74,7 +79,7 @@ public class RootUriTemplateHandler implements UriTemplateHandler { return this.handler.expand(apply(uriTemplate), uriVariables); } - private String apply(String uriTemplate) { + String apply(String uriTemplate) { if (StringUtils.startsWithIgnoreCase(uriTemplate, "/")) { return getRootUri() + uriTemplate; } @@ -91,7 +96,9 @@ public class RootUriTemplateHandler implements UriTemplateHandler { * @param wrapper the wrapper to apply to the delegate URI template handler * @return the new handler * @since 2.3.10 + * @deprecated since 3.2.3 for removal in 3.4.0, with no replacement */ + @Deprecated(since = "3.2.3", forRemoval = true) public RootUriTemplateHandler withHandlerWrapper(Function wrapper) { return new RootUriTemplateHandler(getRootUri(), wrapper.apply(this.handler)); } @@ -101,7 +108,9 @@ public class RootUriTemplateHandler implements UriTemplateHandler { * @param restTemplate the {@link RestTemplate} to add the handler to * @param rootUri the root URI * @return the added {@link RootUriTemplateHandler}. + * @deprecated since 3.2.3 for removal in 3.4.0, with no replacement */ + @Deprecated(since = "3.2.3", forRemoval = true) public static RootUriTemplateHandler addTo(RestTemplate restTemplate, String rootUri) { Assert.notNull(restTemplate, "RestTemplate must not be null"); RootUriTemplateHandler handler = new RootUriTemplateHandler(rootUri, restTemplate.getUriTemplateHandler()); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderTests.java index c4d1e8ea409..fc617087faf 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderTests.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. @@ -126,7 +126,7 @@ class RestTemplateBuilderTests { .build(); UriTemplateHandler handler = template.getUriTemplateHandler(); handler.expand("/hello"); - assertThat(handler).isInstanceOf(RootUriTemplateHandler.class); + assertThat(handler).isInstanceOf(RootUriBuilderFactory.class); then(uriTemplateHandler).should().expand("https://example.com/hello"); } @@ -439,7 +439,7 @@ class RestTemplateBuilderTests { .customizers((restTemplate) -> { assertThat(restTemplate.getInterceptors()).hasSize(1); assertThat(restTemplate.getMessageConverters()).contains(this.messageConverter); - assertThat(restTemplate.getUriTemplateHandler()).isInstanceOf(RootUriTemplateHandler.class); + assertThat(restTemplate.getUriTemplateHandler()).isInstanceOf(RootUriBuilderFactory.class); assertThat(restTemplate.getErrorHandler()).isEqualTo(errorHandler); ClientHttpRequestFactory actualRequestFactory = restTemplate.getRequestFactory(); assertThat(actualRequestFactory).isInstanceOf(InterceptingClientHttpRequestFactory.class); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RootUriBuilderFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RootUriBuilderFactoryTests.java new file mode 100644 index 00000000000..1a77773cd74 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RootUriBuilderFactoryTests.java @@ -0,0 +1,43 @@ +/* + * 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.web.client; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.junit.jupiter.api.Test; + +import org.springframework.web.util.UriBuilder; +import org.springframework.web.util.UriBuilderFactory; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link RootUriBuilderFactory}. + * + * @author Scott Frederick + */ +class RootUriBuilderFactoryTests { + + @Test + void uriStringPrefixesRoot() throws URISyntaxException { + UriBuilderFactory builderFactory = new RootUriBuilderFactory("https://example.com"); + UriBuilder builder = builderFactory.uriString("/hello"); + assertThat(builder.build()).isEqualTo(new URI("https://example.com/hello")); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RootUriTemplateHandlerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RootUriTemplateHandlerTests.java index 2e814000892..56288dc3d71 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RootUriTemplateHandlerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RootUriTemplateHandlerTests.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. @@ -43,6 +43,7 @@ import static org.mockito.BDDMockito.then; * @author Phillip Webb */ @ExtendWith(MockitoExtension.class) +@SuppressWarnings("removal") class RootUriTemplateHandlerTests { private URI uri;