/*
 * Copyright 2012-2017 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.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
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.AbstractClientHttpRequestFactoryWrapper;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.client.support.BasicAuthorizationInterceptor;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
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}.
 * <p>
 * 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
 * @author Andy Wilkinson
 * @since 1.4.0
 */
public class RestTemplateBuilder {

	private static final Map<String, String> REQUEST_FACTORY_CANDIDATES;

	static {
		Map<String, String> 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");
		REQUEST_FACTORY_CANDIDATES = Collections.unmodifiableMap(candidates);
	}

	private final boolean detectRequestFactory;

	private final String rootUri;

	private final Set<HttpMessageConverter<?>> messageConverters;

	private final ClientHttpRequestFactory requestFactory;

	private final UriTemplateHandler uriTemplateHandler;

	private final ResponseErrorHandler errorHandler;

	private final BasicAuthorizationInterceptor basicAuthorization;

	private final Set<RestTemplateCustomizer> restTemplateCustomizers;

	private final Set<RequestFactoryCustomizer> requestFactoryCustomizers;

	private final Set<ClientHttpRequestInterceptor> interceptors;

	/**
	 * 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.restTemplateCustomizers = Collections
				.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(customizers)));
		this.requestFactoryCustomizers = Collections.<RequestFactoryCustomizer>emptySet();
		this.interceptors = Collections.<ClientHttpRequestInterceptor>emptySet();
	}

	private RestTemplateBuilder(boolean detectRequestFactory, String rootUri,
			Set<HttpMessageConverter<?>> messageConverters,
			ClientHttpRequestFactory requestFactory,
			UriTemplateHandler uriTemplateHandler, ResponseErrorHandler errorHandler,
			BasicAuthorizationInterceptor basicAuthorization,
			Set<RestTemplateCustomizer> restTemplateCustomizers,
			Set<RequestFactoryCustomizer> requestFactoryCustomizers,
			Set<ClientHttpRequestInterceptor> interceptors) {
		super();
		this.detectRequestFactory = detectRequestFactory;
		this.rootUri = rootUri;
		this.messageConverters = messageConverters;
		this.requestFactory = requestFactory;
		this.uriTemplateHandler = uriTemplateHandler;
		this.errorHandler = errorHandler;
		this.basicAuthorization = basicAuthorization;
		this.restTemplateCustomizers = restTemplateCustomizers;
		this.requestFactoryCustomizers = requestFactoryCustomizers;
		this.interceptors = interceptors;
	}

	/**
	 * 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.restTemplateCustomizers,
				this.requestFactoryCustomizers, this.interceptors);
	}

	/**
	 * 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.restTemplateCustomizers,
				this.requestFactoryCustomizers, this.interceptors);
	}

	/**
	 * Set the {@link HttpMessageConverter HttpMessageConverters} that should be used with
	 * the {@link RestTemplate}. Setting this value will replace any previously configured
	 * converters.
	 * @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 configured
	 * converters.
	 * @param messageConverters the converters to set
	 * @return a new builder instance
	 * @see #additionalMessageConverters(HttpMessageConverter...)
	 */
	public RestTemplateBuilder messageConverters(
			Collection<? extends HttpMessageConverter<?>> messageConverters) {
		Assert.notNull(messageConverters, "MessageConverters must not be null");
		return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
				Collections.unmodifiableSet(
						new LinkedHashSet<HttpMessageConverter<?>>(messageConverters)),
				this.requestFactory, this.uriTemplateHandler, this.errorHandler,
				this.basicAuthorization, this.restTemplateCustomizers,
				this.requestFactoryCustomizers, this.interceptors);
	}

	/**
	 * 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<? extends HttpMessageConverter<?>> 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.restTemplateCustomizers, this.requestFactoryCustomizers,
				this.interceptors);
	}

	/**
	 * 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.restTemplateCustomizers,
				this.requestFactoryCustomizers, this.interceptors);
	}

	/**
	 * Set the {@link ClientHttpRequestInterceptor ClientHttpRequestInterceptors} that
	 * should be used with the {@link RestTemplate}. Setting this value will replace any
	 * previously defined interceptors.
	 * @param interceptors the interceptors to set
	 * @return a new builder instance
	 * @since 1.4.1
	 * @see #additionalInterceptors(ClientHttpRequestInterceptor...)
	 */
	public RestTemplateBuilder interceptors(
			ClientHttpRequestInterceptor... interceptors) {
		Assert.notNull(interceptors, "interceptors must not be null");
		return interceptors(Arrays.asList(interceptors));
	}

	/**
	 * Set the {@link ClientHttpRequestInterceptor ClientHttpRequestInterceptors} that
	 * should be used with the {@link RestTemplate}. Setting this value will replace any
	 * previously defined interceptors.
	 * @param interceptors the interceptors to set
	 * @return a new builder instance
	 * @since 1.4.1
	 * @see #additionalInterceptors(ClientHttpRequestInterceptor...)
	 */
	public RestTemplateBuilder interceptors(
			Collection<ClientHttpRequestInterceptor> interceptors) {
		Assert.notNull(interceptors, "interceptors must not be null");
		return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
				this.messageConverters, this.requestFactory, this.uriTemplateHandler,
				this.errorHandler, this.basicAuthorization, this.restTemplateCustomizers,
				this.requestFactoryCustomizers,
				Collections.unmodifiableSet(new LinkedHashSet<>(interceptors)));
	}

	/**
	 * Add additional {@link ClientHttpRequestInterceptor ClientHttpRequestInterceptors}
	 * that should be used with the {@link RestTemplate}.
	 * @param interceptors the interceptors to add
	 * @return a new builder instance
	 * @since 1.4.1
	 * @see #interceptors(ClientHttpRequestInterceptor...)
	 */
	public RestTemplateBuilder additionalInterceptors(
			ClientHttpRequestInterceptor... interceptors) {
		Assert.notNull(interceptors, "interceptors must not be null");
		return additionalInterceptors(Arrays.asList(interceptors));
	}

	/**
	 * Add additional {@link ClientHttpRequestInterceptor ClientHttpRequestInterceptors}
	 * that should be used with the {@link RestTemplate}.
	 * @param interceptors the interceptors to add
	 * @return a new builder instance
	 * @since 1.4.1
	 * @see #interceptors(ClientHttpRequestInterceptor...)
	 */
	public RestTemplateBuilder additionalInterceptors(
			Collection<? extends ClientHttpRequestInterceptor> interceptors) {
		Assert.notNull(interceptors, "interceptors must not be null");
		return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
				this.messageConverters, this.requestFactory, this.uriTemplateHandler,
				this.errorHandler, this.basicAuthorization, this.restTemplateCustomizers,
				this.requestFactoryCustomizers, append(this.interceptors, interceptors));
	}

	/**
	 * 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<? extends ClientHttpRequestFactory> requestFactory) {
		Assert.notNull(requestFactory, "RequestFactory must not be null");
		return requestFactory(createRequestFactory(requestFactory));
	}

	private ClientHttpRequestFactory createRequestFactory(
			Class<? extends ClientHttpRequestFactory> requestFactory) {
		try {
			Constructor<?> constructor = requestFactory.getDeclaredConstructor();
			constructor.setAccessible(true);
			return (ClientHttpRequestFactory) constructor.newInstance();
		}
		catch (Exception ex) {
			throw new IllegalStateException(ex);
		}
	}

	/**
	 * 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.restTemplateCustomizers,
				this.requestFactoryCustomizers, this.interceptors);
	}

	/**
	 * 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.restTemplateCustomizers,
				this.requestFactoryCustomizers, this.interceptors);
	}

	/**
	 * Set the {@link ResponseErrorHandler} that should be used with the
	 * {@link RestTemplate}.
	 * @param errorHandler the error handler 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.restTemplateCustomizers,
				this.requestFactoryCustomizers, this.interceptors);
	}

	/**
	 * 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.restTemplateCustomizers, this.requestFactoryCustomizers,
				this.interceptors);
	}

	/**
	 * Set the {@link RestTemplateCustomizer RestTemplateCustomizers} 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 RestTemplateCustomizer RestTemplateCustomizers} 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<? extends RestTemplateCustomizer> 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<RestTemplateCustomizer>(
						restTemplateCustomizers)),
				this.requestFactoryCustomizers, this.interceptors);
	}

	/**
	 * Add {@link RestTemplateCustomizer RestTemplateCustomizers} 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 RestTemplateCustomizer RestTemplateCustomizers} 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<? extends RestTemplateCustomizer> 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.restTemplateCustomizers, customizers),
				this.requestFactoryCustomizers, this.interceptors);
	}

	/**
	 * Sets the connect timeout in milliseconds on the underlying
	 * {@link ClientHttpRequestFactory}.
	 * @param connectTimeout the connect timeout in milliseconds
	 * @return a new builder instance.
	 */
	public RestTemplateBuilder setConnectTimeout(int connectTimeout) {
		return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
				this.messageConverters, this.requestFactory, this.uriTemplateHandler,
				this.errorHandler, this.basicAuthorization, this.restTemplateCustomizers,
				append(this.requestFactoryCustomizers,
						new ConnectTimeoutRequestFactoryCustomizer(connectTimeout)),
				this.interceptors);
	}

	/**
	 * Sets the read timeout in milliseconds on the underlying
	 * {@link ClientHttpRequestFactory}.
	 * @param readTimeout the read timeout in milliseconds
	 * @return a new builder instance.
	 */
	public RestTemplateBuilder setReadTimeout(int readTimeout) {
		return new RestTemplateBuilder(this.detectRequestFactory, this.rootUri,
				this.messageConverters, this.requestFactory, this.uriTemplateHandler,
				this.errorHandler, this.basicAuthorization, this.restTemplateCustomizers,
				append(this.requestFactoryCustomizers,
						new ReadTimeoutRequestFactoryCustomizer(readTimeout)),
				this.interceptors);
	}

	/**
	 * 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 <T> 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 extends RestTemplate> T build(Class<T> restTemplateClass) {
		return configure(BeanUtils.instantiateClass(restTemplateClass));
	}

	/**
	 * Configure the provided {@link RestTemplate} instance using this builder.
	 * @param <T> 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 extends RestTemplate> T configure(T restTemplate) {
		configureRequestFactory(restTemplate);
		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.restTemplateCustomizers)) {
			for (RestTemplateCustomizer customizer : this.restTemplateCustomizers) {
				customizer.customize(restTemplate);
			}
		}
		restTemplate.getInterceptors().addAll(this.interceptors);
		return restTemplate;
	}

	private void configureRequestFactory(RestTemplate restTemplate) {
		ClientHttpRequestFactory requestFactory = null;
		if (this.requestFactory != null) {
			requestFactory = this.requestFactory;
		}
		else if (this.detectRequestFactory) {
			requestFactory = detectRequestFactory();
		}
		if (requestFactory != null) {
			ClientHttpRequestFactory unwrappedRequestFactory = unwrapRequestFactoryIfNecessary(
					requestFactory);
			for (RequestFactoryCustomizer customizer : this.requestFactoryCustomizers) {
				customizer.customize(unwrappedRequestFactory);
			}
			restTemplate.setRequestFactory(requestFactory);
		}
	}

	private ClientHttpRequestFactory unwrapRequestFactoryIfNecessary(
			ClientHttpRequestFactory requestFactory) {
		if (!(requestFactory instanceof AbstractClientHttpRequestFactoryWrapper)) {
			return requestFactory;
		}
		ClientHttpRequestFactory unwrappedRequestFactory = requestFactory;
		Field field = ReflectionUtils.findField(
				AbstractClientHttpRequestFactoryWrapper.class, "requestFactory");
		ReflectionUtils.makeAccessible(field);
		do {
			unwrappedRequestFactory = (ClientHttpRequestFactory) ReflectionUtils
					.getField(field, unwrappedRequestFactory);
		}
		while (unwrappedRequestFactory instanceof AbstractClientHttpRequestFactoryWrapper);
		return unwrappedRequestFactory;
	}

	private ClientHttpRequestFactory detectRequestFactory() {
		for (Map.Entry<String, String> 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
						.instantiateClass(factoryClass);
			}
		}
		return new SimpleClientHttpRequestFactory();
	}

	private <T> Set<T> append(Set<T> set, T addition) {
		Set<T> result = new LinkedHashSet<>(
				set == null ? Collections.<T>emptySet() : set);
		result.add(addition);
		return Collections.unmodifiableSet(result);
	}

	private <T> Set<T> append(Set<T> set, Collection<? extends T> additions) {
		Set<T> result = new LinkedHashSet<>(
				set == null ? Collections.<T>emptySet() : set);
		result.addAll(additions);
		return Collections.unmodifiableSet(result);
	}

	/**
	 * Strategy interface used to customize the {@link ClientHttpRequestFactory}.
	 */
	private interface RequestFactoryCustomizer {

		void customize(ClientHttpRequestFactory factory);

	}

	/**
	 * {@link RequestFactoryCustomizer} to call a "set timeout" method.
	 */
	private static abstract class TimeoutRequestFactoryCustomizer
			implements RequestFactoryCustomizer {

		private final int timeout;

		private final String methodName;

		TimeoutRequestFactoryCustomizer(int timeout, String methodName) {
			this.timeout = timeout;
			this.methodName = methodName;
		}

		@Override
		public void customize(ClientHttpRequestFactory factory) {
			ReflectionUtils.invokeMethod(findMethod(factory), factory, this.timeout);
		}

		private Method findMethod(ClientHttpRequestFactory factory) {
			Method method = ReflectionUtils.findMethod(factory.getClass(),
					this.methodName, int.class);
			if (method != null) {
				return method;
			}
			throw new IllegalStateException("Request factory " + factory.getClass()
					+ " does not have a " + this.methodName + "(int) method");
		}

	}

	/**
	 * {@link RequestFactoryCustomizer} to set the read timeout.
	 */
	private static class ReadTimeoutRequestFactoryCustomizer
			extends TimeoutRequestFactoryCustomizer {

		ReadTimeoutRequestFactoryCustomizer(int readTimeout) {
			super(readTimeout, "setReadTimeout");
		}

	}

	/**
	 * {@link RequestFactoryCustomizer} to set the connect timeout.
	 */
	private static class ConnectTimeoutRequestFactoryCustomizer
			extends TimeoutRequestFactoryCustomizer {

		ConnectTimeoutRequestFactoryCustomizer(int connectTimeout) {
			super(connectTimeout, "setConnectTimeout");
		}

	}

}
