From b641e63466c1bca0e705b2db539adb7dec107004 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 31 May 2016 09:28:14 -0700 Subject: [PATCH] Add RestTemplateBuilder support Add a RestTemplateBuilder that allows RestTemplates to be easily created and configured. See gh-5507 --- .../client/LocalHostUriTemplateHandler.java | 6 +- .../test/web/client/TestRestTemplate.java | 30 +- .../LocalHostUriTemplateHandlerTests.java | 4 +- .../client/BasicAuthorizationInterceptor.java | 58 +++ .../boot/web/client/RestTemplateBuilder.java | 438 ++++++++++++++++++ .../web/client/RestTemplateCustomizer.java | 36 ++ .../web/client/RootUriTemplateHandler.java | 101 ++++ .../BasicAuthorizationInterceptorTests.java | 73 +++ .../web/client/RestTemplateBuilderTests.java | 370 +++++++++++++++ .../client/RootUriTemplateHandlerTests.java | 127 +++++ 10 files changed, 1210 insertions(+), 33 deletions(-) create mode 100644 spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthorizationInterceptor.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateCustomizer.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/web/client/BasicAuthorizationInterceptorTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/web/client/RootUriTemplateHandlerTests.java diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/LocalHostUriTemplateHandler.java b/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/LocalHostUriTemplateHandler.java index 1e4b4a3f86..84a4061050 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/LocalHostUriTemplateHandler.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/LocalHostUriTemplateHandler.java @@ -16,6 +16,7 @@ package org.springframework.boot.test.web.client; +import org.springframework.boot.web.client.RootUriTemplateHandler; import org.springframework.core.env.Environment; import org.springframework.util.Assert; import org.springframework.web.util.DefaultUriTemplateHandler; @@ -28,17 +29,18 @@ import org.springframework.web.util.UriTemplateHandler; * @author Phillip Webb * @since 1.4.0 */ -public class LocalHostUriTemplateHandler extends DefaultUriTemplateHandler { +public class LocalHostUriTemplateHandler extends RootUriTemplateHandler { private final Environment environment; public LocalHostUriTemplateHandler(Environment environment) { + super(new DefaultUriTemplateHandler()); Assert.notNull(environment, "Environment must not be null"); this.environment = environment; } @Override - public String getBaseUrl() { + public String getRootUri() { String port = this.environment.getProperty("local.server.port", "8080"); return "http://localhost:" + port; } diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java b/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java index 5759c3790c..08990f8665 100644 --- a/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java @@ -18,7 +18,6 @@ package org.springframework.boot.test.web.client; import java.io.IOException; import java.net.URI; -import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -37,20 +36,18 @@ import org.apache.http.impl.client.HttpClients; import org.apache.http.protocol.HttpContext; import org.apache.http.ssl.SSLContextBuilder; +import org.springframework.boot.web.client.BasicAuthorizationInterceptor; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; -import org.springframework.http.HttpRequest; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; -import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.InterceptingClientHttpRequestFactory; import org.springframework.util.Assert; -import org.springframework.util.Base64Utils; import org.springframework.util.ClassUtils; import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RequestCallback; @@ -76,8 +73,6 @@ import org.springframework.web.util.UriTemplateHandler; */ public class TestRestTemplate { - private static final Charset UTF_8 = Charset.forName("UTF-8"); - private final RestTemplate restTemplate; /** @@ -912,29 +907,6 @@ public class TestRestTemplate { } - private static class BasicAuthorizationInterceptor - implements ClientHttpRequestInterceptor { - - private final String username; - - private final String password; - - BasicAuthorizationInterceptor(String username, String password) { - this.username = username; - this.password = (password == null ? "" : password); - } - - @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] body, - ClientHttpRequestExecution execution) throws IOException { - String token = Base64Utils.encodeToString( - (this.username + ":" + this.password).getBytes(UTF_8)); - request.getHeaders().add("Authorization", "Basic " + token); - return execution.execute(request, body); - } - - } - /** * {@link HttpComponentsClientHttpRequestFactory} to apply customizations. */ diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/LocalHostUriTemplateHandlerTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/LocalHostUriTemplateHandlerTests.java index c056cd4f41..7b9c5cf9ad 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/LocalHostUriTemplateHandlerTests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/LocalHostUriTemplateHandlerTests.java @@ -47,7 +47,7 @@ public class LocalHostUriTemplateHandlerTests { environment.setProperty("local.server.port", "1234"); LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler( environment); - assertThat(handler.getBaseUrl()).isEqualTo("http://localhost:1234"); + assertThat(handler.getRootUri()).isEqualTo("http://localhost:1234"); } @Test @@ -55,7 +55,7 @@ public class LocalHostUriTemplateHandlerTests { MockEnvironment environment = new MockEnvironment(); LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler( environment); - assertThat(handler.getBaseUrl()).isEqualTo("http://localhost:8080"); + assertThat(handler.getRootUri()).isEqualTo("http://localhost:8080"); } } diff --git a/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthorizationInterceptor.java b/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthorizationInterceptor.java new file mode 100644 index 0000000000..e994245d08 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthorizationInterceptor.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.io.IOException; +import java.nio.charset.Charset; + +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.Assert; +import org.springframework.util.Base64Utils; + +/** + * {@link ClientHttpRequestInterceptor} to apply a BASIC authorization header. + * + * @author Phillip Webb + * @since 1.4.0 + */ +public class BasicAuthorizationInterceptor implements ClientHttpRequestInterceptor { + + private static final Charset UTF_8 = Charset.forName("UTF-8"); + + private final String username; + + private final String password; + + public BasicAuthorizationInterceptor(String username, String password) { + Assert.hasLength(username, "Username must not be empty"); + this.username = username; + this.password = (password == null ? "" : password); + } + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, + ClientHttpRequestExecution execution) throws IOException { + String token = Base64Utils + .encodeToString((this.username + ":" + this.password).getBytes(UTF_8)); + request.getHeaders().add("Authorization", "Basic " + token); + return execution.execute(request, body); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java b/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java new file mode 100644 index 0000000000..ee64a5ec55 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java @@ -0,0 +1,438 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.springframework.beans.BeanUtils; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriTemplateHandler; + +/** + * Builder that can be used to configure and create a {@link RestTemplate}. Provides + * convenience methods to register {@link #messageConverters(HttpMessageConverter...) + * converters}, {@link #errorHandler(ResponseErrorHandler) error handlers} and + * {@link #uriTemplateHandler(UriTemplateHandler) UriTemplateHandlers}. + *

+ * By default the built {@link RestTemplate} will attempt to use the most suitable + * {@link ClientHttpRequestFactory}, call {@link #detectRequestFactory(boolean) + * detectRequestFactory(false)} if you prefer to keep the default. In a typical + * auto-configured Spring Boot application this builder is available as a bean and can be + * injected whenever a {@link RestTemplate} is needed. + * + * @author Stephane Nicoll + * @author Phillip Webb + * @since 1.4.0 + */ +public class RestTemplateBuilder { + + private static final Map REQUEST_FACTORY_CANDIDATES; + + static { + Map candidates = new LinkedHashMap(); + candidates.put("org.apache.http.client.HttpClient", + "org.springframework.http.client.HttpComponentsClientHttpRequestFactory"); + candidates.put("okhttp3.OkHttpClient", + "org.springframework.http.client.OkHttp3ClientHttpRequestFactory"); + candidates.put("com.squareup.okhttp.OkHttpClient", + "org.springframework.http.client.OkHttpClientHttpRequestFactory"); + candidates.put("io.netty.channel.EventLoopGroup", + "org.springframework.http.client.Netty4ClientHttpRequestFactory"); + REQUEST_FACTORY_CANDIDATES = Collections.unmodifiableMap(candidates); + } + + private final boolean detectRequestFactory; + + private final String rootUri; + + private final Set> messageConverters; + + private final ClientHttpRequestFactory requestFactory; + + private final UriTemplateHandler uriTemplateHandler; + + private final ResponseErrorHandler errorHandler; + + private final BasicAuthorizationInterceptor basicAuthorization; + + private final Set customizers; + + /** + * Create a new {@link RestTemplateBuilder} instance. + * @param customizers any {@link RestTemplateCustomizer RestTemplateCustomizers} that + * should be applied when the {@link RestTemplate} is built + */ + public RestTemplateBuilder(RestTemplateCustomizer... customizers) { + Assert.notNull(customizers, "Customizers must not be null"); + this.detectRequestFactory = true; + this.rootUri = null; + this.messageConverters = null; + this.requestFactory = null; + this.uriTemplateHandler = null; + this.errorHandler = null; + this.basicAuthorization = null; + this.customizers = Collections.unmodifiableSet( + new LinkedHashSet(Arrays.asList(customizers))); + } + + private RestTemplateBuilder(boolean detectRequestFactory, String rootUri, + Set> messageConverters, + ClientHttpRequestFactory requestFactory, + UriTemplateHandler uriTemplateHandler, ResponseErrorHandler errorHandler, + BasicAuthorizationInterceptor basicAuthorization, + Set customizers) { + super(); + this.detectRequestFactory = detectRequestFactory; + this.rootUri = rootUri; + this.messageConverters = messageConverters; + this.requestFactory = requestFactory; + this.uriTemplateHandler = uriTemplateHandler; + this.errorHandler = errorHandler; + this.basicAuthorization = basicAuthorization; + this.customizers = customizers; + } + + /** + * Set if the {@link ClientHttpRequestFactory} should be detected based on the + * classpath. Default if {@code true}. + * @param detectRequestFactory if the {@link ClientHttpRequestFactory} should be + * detected + * @return a new builder instance + */ + public RestTemplateBuilder detectRequestFactory(boolean detectRequestFactory) { + return new RestTemplateBuilder(detectRequestFactory, this.rootUri, + this.messageConverters, this.requestFactory, this.uriTemplateHandler, + this.errorHandler, this.basicAuthorization, this.customizers); + } + + /** + * Set a root URL that should be applied to each request that starts with {@code '/'}. + * See {@link RootUriTemplateHandler} for details. + * @param rootUri the root URI or {@code null} + * @return a new builder instance + */ + public RestTemplateBuilder rootUri(String rootUri) { + return new RestTemplateBuilder(this.detectRequestFactory, rootUri, + this.messageConverters, this.requestFactory, this.uriTemplateHandler, + this.errorHandler, this.basicAuthorization, this.customizers); + } + + /** + * Set the {@link HttpMessageConverter HttpMessageConverters} that should be used with + * the {@link RestTemplate}. Setting this value will replace any previously calls. + * @param messageConverters the converters to set + * @return a new builder instance + * @see #additionalMessageConverters(HttpMessageConverter...) + */ + public RestTemplateBuilder messageConverters( + HttpMessageConverter... messageConverters) { + Assert.notNull(messageConverters, "MessageConverters must not be null"); + return messageConverters(Arrays.asList(messageConverters)); + } + + /** + * Set the {@link HttpMessageConverter HttpMessageConverters} that should be used with + * the {@link RestTemplate}. Setting this value will replace any previously calls. + * @param messageConverters the converters to set + * @return a new builder instance + * @see #additionalMessageConverters(HttpMessageConverter...) + */ + public RestTemplateBuilder messageConverters( + Collection> messageConverters) { + Assert.notNull(messageConverters, "MessageConverters must not be null"); + return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, + Collections.unmodifiableSet( + new LinkedHashSet>(messageConverters)), + this.requestFactory, this.uriTemplateHandler, this.errorHandler, + this.basicAuthorization, this.customizers); + } + + /** + * Add additional {@link HttpMessageConverter HttpMessageConverters} that should be + * used with the {@link RestTemplate}. + * @param messageConverters the converters to add + * @return a new builder instance + * @see #messageConverters(HttpMessageConverter...) + */ + public RestTemplateBuilder additionalMessageConverters( + HttpMessageConverter... messageConverters) { + Assert.notNull(messageConverters, "MessageConverters must not be null"); + return additionalMessageConverters(Arrays.asList(messageConverters)); + } + + /** + * Add additional {@link HttpMessageConverter HttpMessageConverters} that should be + * used with the {@link RestTemplate}. + * @param messageConverters the converters to add + * @return a new builder instance + * @see #messageConverters(HttpMessageConverter...) + */ + public RestTemplateBuilder additionalMessageConverters( + Collection> messageConverters) { + Assert.notNull(messageConverters, "MessageConverters must not be null"); + return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, + append(this.messageConverters, messageConverters), this.requestFactory, + this.uriTemplateHandler, this.errorHandler, this.basicAuthorization, + this.customizers); + } + + /** + * Set the {@link HttpMessageConverter HttpMessageConverters} that should be used with + * the {@link RestTemplate} to the default set. Calling this method will replace any + * previously defined converters. + * @return a new builder instance + * @see #messageConverters(HttpMessageConverter...) + */ + public RestTemplateBuilder defaultMessageConverters() { + return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, + Collections.unmodifiableSet(new LinkedHashSet>( + new RestTemplate().getMessageConverters())), + this.requestFactory, this.uriTemplateHandler, this.errorHandler, + this.basicAuthorization, this.customizers); + } + + /** + * Set the {@link ClientHttpRequestFactory} class that should be used with the + * {@link RestTemplate}. + * @param requestFactory the request factory to use + * @return a new builder instance + */ + public RestTemplateBuilder requestFactory( + Class requestFactory) { + Assert.notNull(requestFactory, "RequestFactory must not be null"); + return requestFactory(BeanUtils.instantiate(requestFactory)); + } + + /** + * Set the {@link ClientHttpRequestFactory} that should be used with the + * {@link RestTemplate}. + * @param requestFactory the request factory to use + * @return a new builder instance + */ + public RestTemplateBuilder requestFactory(ClientHttpRequestFactory requestFactory) { + Assert.notNull(requestFactory, "RequestFactory must not be null"); + return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, + this.messageConverters, requestFactory, this.uriTemplateHandler, + this.errorHandler, this.basicAuthorization, this.customizers); + } + + /** + * Set the {@link UriTemplateHandler} that should be used with the + * {@link RestTemplate}. + * @param uriTemplateHandler the URI template handler to use + * @return a new builder instance + */ + public RestTemplateBuilder uriTemplateHandler(UriTemplateHandler uriTemplateHandler) { + Assert.notNull(uriTemplateHandler, "UriTemplateHandler must not be null"); + return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, + this.messageConverters, this.requestFactory, uriTemplateHandler, + this.errorHandler, this.basicAuthorization, this.customizers); + } + + /** + * Set the {@link ResponseErrorHandler} that should be used with the + * {@link RestTemplate}. + * @param errorHandler the error hander to use + * @return a new builder instance + */ + public RestTemplateBuilder errorHandler(ResponseErrorHandler errorHandler) { + Assert.notNull(errorHandler, "ErrorHandler must not be null"); + return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, + this.messageConverters, this.requestFactory, this.uriTemplateHandler, + errorHandler, this.basicAuthorization, this.customizers); + } + + /** + * Add HTTP basic authentication to requests. See + * {@link BasicAuthorizationInterceptor} for details. + * @param username the user name + * @param password the password + * @return a new builder instance + */ + public RestTemplateBuilder basicAuthorization(String username, String password) { + return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, + this.messageConverters, this.requestFactory, this.uriTemplateHandler, + this.errorHandler, new BasicAuthorizationInterceptor(username, password), + this.customizers); + } + + /** + * Set the {@link HttpMessageConverter HttpMessageConverters} that should be applied + * to the {@link RestTemplate}. Customizers are applied in the order that they were + * added after builder configuration has been applied. Setting this value will replace + * any previously configured customizers. + * @param restTemplateCustomizers the customizers to set + * @return a new builder instance + * @see #additionalCustomizers(RestTemplateCustomizer...) + */ + public RestTemplateBuilder customizers( + RestTemplateCustomizer... restTemplateCustomizers) { + Assert.notNull(restTemplateCustomizers, + "RestTemplateCustomizers must not be null"); + return customizers(Arrays.asList(restTemplateCustomizers)); + } + + /** + * Set the {@link HttpMessageConverter HttpMessageConverters} that should be applied + * to the {@link RestTemplate}. Customizers are applied in the order that they were + * added after builder configuration has been applied. Setting this value will replace + * any previously configured customizers. + * @param restTemplateCustomizers the customizers to set + * @return a new builder instance + * @see #additionalCustomizers(RestTemplateCustomizer...) + */ + public RestTemplateBuilder customizers( + Collection restTemplateCustomizers) { + Assert.notNull(restTemplateCustomizers, + "RestTemplateCustomizers must not be null"); + return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, + this.messageConverters, this.requestFactory, this.uriTemplateHandler, + this.errorHandler, this.basicAuthorization, + Collections.unmodifiableSet(new LinkedHashSet( + restTemplateCustomizers))); + } + + /** + * Add {@link HttpMessageConverter HttpMessageConverters} that should be applied to + * the {@link RestTemplate}. Customizers are applied in the order that they were added + * after builder configuration has been applied. + * @param restTemplateCustomizers the customizers to add + * @return a new builder instance + * @see #customizers(RestTemplateCustomizer...) + */ + public RestTemplateBuilder additionalCustomizers( + RestTemplateCustomizer... restTemplateCustomizers) { + Assert.notNull(restTemplateCustomizers, + "RestTemplateCustomizers must not be null"); + return additionalCustomizers(Arrays.asList(restTemplateCustomizers)); + } + + /** + * Add {@link HttpMessageConverter HttpMessageConverters} that should be applied to + * the {@link RestTemplate}. Customizers are applied in the order that they were added + * after builder configuration has been applied. + * @param customizers the customizers to add + * @return a new builder instance + * @see #customizers(RestTemplateCustomizer...) + */ + public RestTemplateBuilder additionalCustomizers( + Collection customizers) { + Assert.notNull(customizers, "RestTemplateCustomizers must not be null"); + return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri, + this.messageConverters, this.requestFactory, this.uriTemplateHandler, + this.errorHandler, this.basicAuthorization, + append(this.customizers, customizers)); + } + + /** + * Build a new {@link RestTemplate} instance and configure it using this builder. + * @return a configured {@link RestTemplate} instance. + * @see #build(Class) + * @see #configure(RestTemplate) + */ + public RestTemplate build() { + return build(RestTemplate.class); + } + + /** + * Build a new {@link RestTemplate} instance of the specified type and configure it + * using this builder. + * @param the type of rest template + * @param restTemplateClass the template type to create + * @return a configured {@link RestTemplate} instance. + * @see RestTemplateBuilder#build() + * @see #configure(RestTemplate) + */ + + public T build(Class restTemplateClass) { + return configure(BeanUtils.instantiate(restTemplateClass)); + } + + /** + * Configure the provided {@link RestTemplate} instance using this builder. + * @param the type of rest template + * @param restTemplate the {@link RestTemplate} to configure + * @return the rest template instance + * @see RestTemplateBuilder#build() + * @see RestTemplateBuilder#build(Class) + */ + public T configure(T restTemplate) { + if (this.requestFactory != null) { + restTemplate.setRequestFactory(this.requestFactory); + } + else if (this.detectRequestFactory) { + restTemplate.setRequestFactory(detectRequestFactory()); + } + if (!CollectionUtils.isEmpty(this.messageConverters)) { + restTemplate.setMessageConverters( + new ArrayList>(this.messageConverters)); + } + if (this.uriTemplateHandler != null) { + restTemplate.setUriTemplateHandler(this.uriTemplateHandler); + } + if (this.errorHandler != null) { + restTemplate.setErrorHandler(this.errorHandler); + } + if (this.rootUri != null) { + RootUriTemplateHandler.addTo(restTemplate, this.rootUri); + } + if (this.basicAuthorization != null) { + restTemplate.getInterceptors().add(this.basicAuthorization); + } + if (!CollectionUtils.isEmpty(this.customizers)) { + for (RestTemplateCustomizer customizer : this.customizers) { + customizer.customize(restTemplate); + } + } + return restTemplate; + } + + private ClientHttpRequestFactory detectRequestFactory() { + for (Map.Entry candidate : REQUEST_FACTORY_CANDIDATES + .entrySet()) { + ClassLoader classLoader = getClass().getClassLoader(); + if (ClassUtils.isPresent(candidate.getKey(), classLoader)) { + Class factoryClass = ClassUtils.resolveClassName(candidate.getValue(), + classLoader); + return (ClientHttpRequestFactory) BeanUtils.instantiate(factoryClass); + } + } + return new SimpleClientHttpRequestFactory(); + } + + private Set append(Set set, Collection additions) { + Set result = new LinkedHashSet( + set == null ? Collections.emptySet() : set); + result.addAll(additions); + return Collections.unmodifiableSet(result); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateCustomizer.java b/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateCustomizer.java new file mode 100644 index 0000000000..fb902d41aa --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateCustomizer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.web.client.RestTemplate; + +/** + * Callback interface that can be used to customize a {@link RestTemplate}. + * + * @author Phillip Webb + * @since 1.4.0 + * @see RestTemplateBuilder + */ +public interface RestTemplateCustomizer { + + /** + * Callback to customize a {@link RestTemplate} instance. + * @param restTemplate the template to customize + */ + void customize(RestTemplate restTemplate); + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java b/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java new file mode 100644 index 0000000000..7d704e3863 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java @@ -0,0 +1,101 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.util.Map; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.DefaultUriTemplateHandler; +import org.springframework.web.util.UriTemplateHandler; + +/** + * {@link UriTemplateHandler} to set the root for URI that start with {@code '/'}. + * + * @author Phillip Webb + * @since 1.4.0 + */ +public class RootUriTemplateHandler implements UriTemplateHandler { + + private final String rootUri; + + private final UriTemplateHandler handler; + + protected RootUriTemplateHandler(UriTemplateHandler handler) { + this.rootUri = null; + this.handler = handler; + } + + /** + * Create a new {@link RootUriTemplateHandler} instance. + * @param rootUri the root URI to used to prefix relative URLs + */ + public RootUriTemplateHandler(String rootUri) { + this(rootUri, new DefaultUriTemplateHandler()); + } + + /** + * Create a new {@link RootUriTemplateHandler} instance. + * @param rootUri the root URI to used to prefix relative URLs + * @param handler the delegate handler + */ + public RootUriTemplateHandler(String rootUri, UriTemplateHandler handler) { + Assert.notNull(rootUri, "RootUri must not be null"); + Assert.notNull(handler, "Handler must not be null"); + this.rootUri = rootUri; + this.handler = handler; + } + + @Override + public URI expand(String uriTemplate, Map uriVariables) { + return this.handler.expand(apply(uriTemplate), uriVariables); + } + + @Override + public URI expand(String uriTemplate, Object... uriVariables) { + return this.handler.expand(apply(uriTemplate), uriVariables); + } + + private String apply(String uriTemplate) { + if (StringUtils.startsWithIgnoreCase(uriTemplate, "/")) { + return getRootUri() + uriTemplate; + } + return uriTemplate; + } + + public String getRootUri() { + return this.rootUri; + } + + /** + * Add a {@link RootUriTemplateHandler} instance to the given {@link RestTemplate}. + * @param restTemplate the {@link RestTemplate} to add the handler to + * @param rootUri the root URI + * @return the added {@link RootUriTemplateHandler}. + */ + public static RootUriTemplateHandler addTo(RestTemplate restTemplate, + String rootUri) { + Assert.notNull(restTemplate, "RestTemplate must not be null"); + RootUriTemplateHandler handler = new RootUriTemplateHandler(rootUri, + restTemplate.getUriTemplateHandler()); + restTemplate.setUriTemplateHandler(handler); + return handler; + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/web/client/BasicAuthorizationInterceptorTests.java b/spring-boot/src/test/java/org/springframework/boot/web/client/BasicAuthorizationInterceptorTests.java new file mode 100644 index 0000000000..75dc196c2d --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/web/client/BasicAuthorizationInterceptorTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.mock.http.client.MockClientHttpRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link BasicAuthorizationInterceptor}. + * + * @author Phillip Webb + */ +public class BasicAuthorizationInterceptorTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void createWhenUsernameIsNullShouldThrowException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Username must not be empty"); + new BasicAuthorizationInterceptor(null, "password"); + } + + @Test + public void createWhenUsernameIsEmptyShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Username must not be empty"); + new BasicAuthorizationInterceptor("", "password"); + } + + @Test + public void createWhenPasswordIsNullShouldUseEmptyPassword() throws Exception { + BasicAuthorizationInterceptor interceptor = new BasicAuthorizationInterceptor( + "username", ""); + assertThat(interceptor).extracting("password").containsExactly(""); + } + + @Test + public void interceptShouldAddHeader() throws Exception { + MockClientHttpRequest request = new MockClientHttpRequest(); + ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); + byte[] body = new byte[] {}; + new BasicAuthorizationInterceptor("spring", "boot").intercept(request, body, + execution); + verify(execution).execute(request, body); + assertThat(request.getHeaders().getFirst("Authorization")) + .isEqualTo("Basic c3ByaW5nOmJvb3Q="); + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderTests.java b/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderTests.java new file mode 100644 index 0000000000..73b2dd63b3 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderTests.java @@ -0,0 +1,370 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.util.Collections; +import java.util.Set; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.ResourceHttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriTemplateHandler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Tests for {@link RestTemplateBuilder}. + * + * @author Stephane Nicoll + * @author Phillip Webb + */ +public class RestTemplateBuilderTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private RestTemplateBuilder builder = new RestTemplateBuilder(); + + @Mock + private HttpMessageConverter messageConverter; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void createWhenCustomizersAreNullShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Customizers must not be null"); + RestTemplateCustomizer[] customizers = null; + new RestTemplateBuilder(customizers); + } + + @Test + public void createWithCustomizersShouldApplyCustomizers() throws Exception { + RestTemplateCustomizer customizer = mock(RestTemplateCustomizer.class); + RestTemplate template = new RestTemplateBuilder(customizer).build(); + verify(customizer).customize(template); + } + + @Test + public void buildShouldDetectRequestFactory() throws Exception { + RestTemplate restTemplate = this.builder.build(); + assertThat(restTemplate.getRequestFactory()) + .isInstanceOf(HttpComponentsClientHttpRequestFactory.class); + } + + @Test + public void detectRequestFactoryWhenFalseShouldDisableDetection() throws Exception { + RestTemplate restTemplate = this.builder.detectRequestFactory(false).build(); + assertThat(restTemplate.getRequestFactory()) + .isInstanceOf(SimpleClientHttpRequestFactory.class); + } + + @Test + public void rootUriShouldApply() throws Exception { + RestTemplate restTemplate = this.builder.rootUri("http://example.com").build(); + MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate).build(); + server.expect(requestTo("http://example.com/hello")).andRespond(withSuccess()); + restTemplate.getForEntity("/hello", String.class); + server.verify(); + } + + @Test + public void rootUriShouldApplyAfterUriTemplateHandler() throws Exception { + UriTemplateHandler uriTemplateHandler = mock(UriTemplateHandler.class); + RestTemplate template = this.builder.uriTemplateHandler(uriTemplateHandler) + .rootUri("http://example.com").build(); + UriTemplateHandler handler = template.getUriTemplateHandler(); + handler.expand("/hello"); + assertThat(handler).isInstanceOf(RootUriTemplateHandler.class); + verify(uriTemplateHandler).expand("http://example.com/hello"); + } + + @Test + public void messageConvertersWhenConvertersAreNullShouldThrowException() + throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("MessageConverters must not be null"); + this.builder.messageConverters((HttpMessageConverter[]) null); + } + + @Test + public void messageConvertersCollectionWhenConvertersAreNullShouldThrowException() + throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("MessageConverters must not be null"); + this.builder.messageConverters((Set>) null); + } + + @Test + public void messageConvertersShouldApply() throws Exception { + RestTemplate template = this.builder.messageConverters(this.messageConverter) + .build(); + assertThat(template.getMessageConverters()).containsOnly(this.messageConverter); + } + + @Test + public void messageConvertersShouldRepalceExisting() throws Exception { + RestTemplate template = this.builder + .messageConverters(new ResourceHttpMessageConverter()) + .messageConverters(Collections.singleton(this.messageConverter)).build(); + assertThat(template.getMessageConverters()).containsOnly(this.messageConverter); + } + + @Test + public void additionalMessageConvertersWhenConvertersAreNullShouldThrowException() + throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("MessageConverters must not be null"); + this.builder.additionalMessageConverters((HttpMessageConverter[]) null); + } + + @Test + public void additionalMessageConvertersCollectionWhenConvertersAreNullShouldThrowException() + throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("MessageConverters must not be null"); + this.builder.additionalMessageConverters((Set>) null); + } + + @Test + public void additionalMessageConvertersShouldAddToExisting() throws Exception { + HttpMessageConverter resourceConverter = new ResourceHttpMessageConverter(); + RestTemplate template = this.builder.messageConverters(resourceConverter) + .additionalMessageConverters(this.messageConverter).build(); + assertThat(template.getMessageConverters()).containsOnly(resourceConverter, + this.messageConverter); + } + + @Test + public void defaultMessageConvertersShouldSetDefaultList() throws Exception { + RestTemplate template = new RestTemplate( + Collections.>singletonList( + new StringHttpMessageConverter())); + this.builder.defaultMessageConverters().configure(template); + assertThat(template.getMessageConverters()) + .hasSameSizeAs(new RestTemplate().getMessageConverters()); + } + + @Test + public void defaultMessageConvertersShouldClearExisting() throws Exception { + RestTemplate template = new RestTemplate( + Collections.>singletonList( + new StringHttpMessageConverter())); + this.builder.additionalMessageConverters(this.messageConverter) + .defaultMessageConverters().configure(template); + assertThat(template.getMessageConverters()) + .hasSameSizeAs(new RestTemplate().getMessageConverters()); + } + + @Test + public void requestFactoryClassWhenFactoryIsNullShouldThrowException() + throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("RequestFactory must not be null"); + this.builder.requestFactory((Class) null); + } + + @Test + public void requestFactoryClassShouldApply() throws Exception { + RestTemplate template = this.builder + .requestFactory(SimpleClientHttpRequestFactory.class).build(); + assertThat(template.getRequestFactory()) + .isInstanceOf(SimpleClientHttpRequestFactory.class); + } + + @Test + public void requestFactoryWhenFactoryIsNullShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("RequestFactory must not be null"); + this.builder.requestFactory((ClientHttpRequestFactory) null); + } + + @Test + public void requestFactoryShouldApply() throws Exception { + ClientHttpRequestFactory requestFactory = mock(ClientHttpRequestFactory.class); + RestTemplate template = this.builder.requestFactory(requestFactory).build(); + assertThat(template.getRequestFactory()).isSameAs(requestFactory); + } + + @Test + public void uriTemplateHandlerWhenHandlerIsNullShouldThrowException() + throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("UriTemplateHandler must not be null"); + this.builder.uriTemplateHandler(null); + } + + @Test + public void uriTemplateHandlerShouldApply() throws Exception { + UriTemplateHandler uriTemplateHandler = mock(UriTemplateHandler.class); + RestTemplate template = this.builder.uriTemplateHandler(uriTemplateHandler) + .build(); + assertThat(template.getUriTemplateHandler()).isSameAs(uriTemplateHandler); + } + + @Test + public void errorHandlerWhenHandlerIsNullShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("ErrorHandler must not be null"); + this.builder.errorHandler(null); + } + + @Test + public void errorHandlerShouldApply() throws Exception { + ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class); + RestTemplate template = this.builder.errorHandler(errorHandler).build(); + assertThat(template.getErrorHandler()).isSameAs(errorHandler); + } + + @Test + public void basicAuthorizationShouldApply() throws Exception { + RestTemplate template = this.builder.basicAuthorization("spring", "boot").build(); + ClientHttpRequestInterceptor interceptor = template.getInterceptors().get(0); + assertThat(interceptor).isInstanceOf(BasicAuthorizationInterceptor.class); + assertThat(interceptor).extracting("username").containsExactly("spring"); + assertThat(interceptor).extracting("password").containsExactly("boot"); + } + + @Test + public void customizersWhenNullArrayShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Customizers must not be null"); + RestTemplateCustomizer[] customizers = null; + this.builder.customizers(customizers); + } + + @Test + public void customizersWhenConvertersAreNullShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("RestTemplateCustomizers must not be null"); + this.builder.customizers((RestTemplateCustomizer[]) null); + } + + @Test + public void customizersCollectionWhenConvertersAreNullShouldThrowException() + throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("RestTemplateCustomizers must not be null"); + this.builder.customizers((Set) null); + } + + @Test + public void customizersShouldApply() throws Exception { + RestTemplateCustomizer customizer = mock(RestTemplateCustomizer.class); + RestTemplate template = this.builder.customizers(customizer).build(); + verify(customizer).customize(template); + } + + @Test + public void customizersShouldBeAppliedLast() throws Exception { + RestTemplate template = spy(new RestTemplate()); + this.builder.additionalCustomizers(new RestTemplateCustomizer() { + + @Override + public void customize(RestTemplate restTemplate) { + verify(restTemplate).setRequestFactory((ClientHttpRequestFactory) any()); + } + + }); + this.builder.configure(template); + } + + @Test + public void customizersShouldRepalceExisting() throws Exception { + RestTemplateCustomizer customizer1 = mock(RestTemplateCustomizer.class); + RestTemplateCustomizer customizer2 = mock(RestTemplateCustomizer.class); + RestTemplate template = this.builder.customizers(customizer1) + .customizers(Collections.singleton(customizer2)).build(); + verifyZeroInteractions(customizer1); + verify(customizer2).customize(template); + } + + @Test + public void additionalCustomizersWhenConvertersAreNullShouldThrowException() + throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("RestTemplateCustomizers must not be null"); + this.builder.additionalCustomizers((RestTemplateCustomizer[]) null); + } + + @Test + public void additionalCustomizersCollectionWhenConvertersAreNullShouldThrowException() + throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("RestTemplateCustomizers must not be null"); + this.builder.additionalCustomizers((Set) null); + } + + @Test + public void additionalCustomizersShouldAddToExisting() throws Exception { + RestTemplateCustomizer customizer1 = mock(RestTemplateCustomizer.class); + RestTemplateCustomizer customizer2 = mock(RestTemplateCustomizer.class); + RestTemplate template = this.builder.customizers(customizer1) + .additionalCustomizers(customizer2).build(); + verify(customizer1).customize(template); + verify(customizer2).customize(template); + } + + @Test + public void buildShouldReturnRestTemplate() throws Exception { + RestTemplate template = this.builder.build(); + assertThat(template.getClass()).isEqualTo(RestTemplate.class); + } + + @Test + public void buildClassShouldReturnClassInstance() throws Exception { + RestTemplateSubclass template = this.builder.build(RestTemplateSubclass.class); + assertThat(template.getClass()).isEqualTo(RestTemplateSubclass.class); + } + + @Test + public void configureShouldApply() throws Exception { + RestTemplate template = new RestTemplate(); + this.builder.configure(template); + assertThat(template.getRequestFactory()) + .isInstanceOf(HttpComponentsClientHttpRequestFactory.class); + } + + public static class RestTemplateSubclass extends RestTemplate { + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/web/client/RootUriTemplateHandlerTests.java b/spring-boot/src/test/java/org/springframework/boot/web/client/RootUriTemplateHandlerTests.java new file mode 100644 index 0000000000..df47ec88c0 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/web/client/RootUriTemplateHandlerTests.java @@ -0,0 +1,127 @@ +/* + * Copyright 2012-2016 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 + * + * http://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 java.util.HashMap; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriTemplateHandler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.anyMap; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.anyVararg; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link RootUriTemplateHandler}. + * + * @author Phillip Webb + */ +public class RootUriTemplateHandlerTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private URI uri; + + @Mock + public UriTemplateHandler delegate; + + public UriTemplateHandler handler; + + @Before + @SuppressWarnings("unchecked") + public void setup() throws URISyntaxException { + MockitoAnnotations.initMocks(this); + this.uri = new URI("http://example.com/hello"); + this.handler = new RootUriTemplateHandler("http://example.com", this.delegate); + given(this.delegate.expand(anyString(), anyMap())).willReturn(this.uri); + given(this.delegate.expand(anyString(), (Object[]) anyVararg())) + .willReturn(this.uri); + } + + @Test + public void createWithNullRootUriShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("RootUri must not be null"); + new RootUriTemplateHandler((String) null); + } + + @Test + public void createWithNullHandlerShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Handler must not be null"); + new RootUriTemplateHandler("http://example.com", null); + } + + @Test + public void expandMapVariablesShouldPrefixRoot() throws Exception { + HashMap uriVariables = new HashMap(); + URI expanded = this.handler.expand("/hello", uriVariables); + verify(this.delegate).expand("http://example.com/hello", uriVariables); + assertThat(expanded).isEqualTo(this.uri); + } + + @Test + public void expandMapVariablesWhenPathDoesNotStartWithSlashShouldNotPrefixRoot() + throws Exception { + HashMap uriVariables = new HashMap(); + URI expanded = this.handler.expand("http://spring.io/hello", uriVariables); + verify(this.delegate).expand("http://spring.io/hello", uriVariables); + assertThat(expanded).isEqualTo(this.uri); + } + + @Test + public void expandArrayVariablesShouldPrefixRoot() throws Exception { + Object[] uriVariables = new Object[0]; + URI expanded = this.handler.expand("/hello", uriVariables); + verify(this.delegate).expand("http://example.com/hello", uriVariables); + assertThat(expanded).isEqualTo(this.uri); + } + + @Test + public void expandArrayVariablesWhenPathDoesNotStartWithSlashShouldNotPrefixRoot() + throws Exception { + Object[] uriVariables = new Object[0]; + URI expanded = this.handler.expand("http://spring.io/hello", uriVariables); + verify(this.delegate).expand("http://spring.io/hello", uriVariables); + assertThat(expanded).isEqualTo(this.uri); + } + + @Test + public void applyShouldWrapExistingTemplate() throws Exception { + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setUriTemplateHandler(this.delegate); + this.handler = RootUriTemplateHandler.addTo(restTemplate, "http://example.com"); + Object[] uriVariables = new Object[0]; + URI expanded = this.handler.expand("/hello", uriVariables); + verify(this.delegate).expand("http://example.com/hello", uriVariables); + assertThat(expanded).isEqualTo(this.uri); + } + +}