SPR-5260: RestTemplate

This commit is contained in:
Arjen Poutsma
2009-02-22 14:51:00 +00:00
parent cdd37d7e8b
commit ca535bb1d0
19 changed files with 1844 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2002-2009 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.web.client;
import org.springframework.web.http.HttpStatus;
/**
* Exception thrown when a HTTP 4xx is received.
*
* @author Arjen Poutsma
* @see org.springframework.web.client.core.SimpleHttpErrorHandler
* @since 3.0
*/
public class HttpClientErrorException extends HttpStatusCodeException {
/**
* Constructs a new instance of {@code HttpClientErrorException} based on a {@link HttpStatus}.
*
* @param statusCode the status code
*/
public HttpClientErrorException(HttpStatus statusCode) {
super(statusCode);
}
/**
* Constructs a new instance of {@code HttpClientErrorException} based on a {@link HttpStatus} and status text.
*
* @param statusCode the status code
* @param statusText the status text
*/
public HttpClientErrorException(HttpStatus statusCode, String statusText) {
super(statusCode, statusText);
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2002-2009 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.web.client;
import org.springframework.core.NestedRuntimeException;
/**
* Base class for exceptions thrown by the framework whenever it encounters client-side HTTP errors.
*
* @author Arjen Poutsma
* @since 3.0
*/
public class HttpClientException extends NestedRuntimeException {
/**
* Constructs a new instance of {@code HttpClientException} with the given message.
*
* @param msg the message
*/
public HttpClientException(String msg) {
super(msg);
}
/**
* Constructs a new instance of {@code HttpClientException} with the given message and exception.
*
* @param msg the message
* @param ex the exception
*/
public HttpClientException(String msg, Throwable ex) {
super(msg, ex);
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2002-2009 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.web.client;
import java.io.IOException;
/**
* Exception thrown when a I/O error occurs.
*
* @author Arjen Poutsma
* @since 3.0
*/
public class HttpIOException extends HttpClientException {
/**
* Constructs a new {@code HttpIOException} with the given message.
*
* @param msg the message
*/
public HttpIOException(String msg) {
super(msg);
}
/**
* Constructs a new {@code HttpIOException} with the given message and {@link IOException}.
*
* @param msg the message
* @param ex the {@code IOException}
*/
public HttpIOException(String msg, IOException ex) {
super(msg, ex);
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2002-2009 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.web.client;
import org.springframework.web.http.HttpStatus;
/**
* Exception thrown when a HTTP 5xx is received.
*
* @author Arjen Poutsma
* @see org.springframework.web.client.core.SimpleHttpErrorHandler
* @since 3.0
*/
public class HttpServerErrorException extends HttpStatusCodeException {
/**
* Constructs a new instance of {@code HttpServerErrorException} based on a {@link HttpStatus}.
*
* @param statusCode the status code
*/
public HttpServerErrorException(HttpStatus statusCode) {
super(statusCode);
}
/**
* Constructs a new instance of {@code HttpServerErrorException} based on a {@link HttpStatus} and status text.
*
* @param statusCode the status code
* @param statusText the status text
*/
public HttpServerErrorException(HttpStatus statusCode, String statusText) {
super(statusCode, statusText);
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2002-2009 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.web.client;
import org.springframework.web.http.HttpStatus;
/**
* Abstract base class for exceptions based on a {@link HttpStatus}.
*
* @author Arjen Poutsma
* @since 3.0
*/
public abstract class HttpStatusCodeException extends HttpClientException {
private final HttpStatus statusCode;
private final String statusText;
/**
* Constructs a new instance of {@code HttpStatusCodeException} based on a {@link HttpStatus}.
*
* @param statusCode the status code
*/
protected HttpStatusCodeException(HttpStatus statusCode) {
super(statusCode.toString());
this.statusCode = statusCode;
this.statusText = statusCode.name();
}
/**
* Constructs a new instance of {@code HttpStatusCodeException} based on a {@link HttpStatus} and status text.
*
* @param statusCode the status code
* @param statusText the status text
*/
protected HttpStatusCodeException(HttpStatus statusCode, String statusText) {
super(statusCode.value() + " " + statusText);
this.statusCode = statusCode;
this.statusText = statusText;
}
/**
* Returns the HTTP status code.
*/
public HttpStatus getStatusCode() {
return statusCode;
}
/**
* Returns the HTTP status text.
*/
public String getStatusText() {
return statusText;
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2002-2009 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.web.client.core;
import java.io.IOException;
import org.springframework.web.http.client.ClientHttpResponse;
/**
* Strategy interface used by the {@link RestTemplate} to determine whether a particular response has an error or not.
*
* @author Arjen Poutsma
* @since 3.0
*/
public interface HttpErrorHandler {
/**
* Indicates whether the given response has any errors.
*
* Implementations will typically inspect the {@link ClientHttpResponse#getStatusCode() HttpStatus} of the response.
*
* @param response the response to inspect
* @return <code>true</code> if the response has an error; <code>false</code> otherwise
* @throws IOException in case of I/O errors
*/
boolean hasError(ClientHttpResponse response) throws IOException;
/**
* Handles the error in the given response.
*
* This method is only called when {@link #hasError(ClientHttpResponse)} has returned <code>true</code>.
*
* @param response the response with the error
* @throws IOException in case of I/O errors
* @throws org.springframework.web.client.HttpClientException
* typically thrown by implementations of this interface
*/
void handleError(ClientHttpResponse response) throws IOException;
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2002-2009 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.web.client.core;
import java.io.IOException;
import org.springframework.web.http.client.ClientHttpRequest;
/**
* Callback interface for code that operates on a {@link ClientHttpRequest}. Allows to manipulate the request
* headers, and write to the request body.
*
* <p>Used internally by the {@link RestTemplate}, but also useful for application code.
*
* @author Arjen Poutsma
* @see RestTemplate#execute
* @since 3.0
*/
public interface HttpRequestCallback {
/**
* Gets called by {@link RestTemplate#execute} with an opened {@code ClientHttpRequest}. Does not need to care about
* closing the request, handling I/O errors, or about handling errors: this will all be handled by the {@code
* RestTemplate}.
*
* @param request the active HTTP request
* @throws IOException in case of I/O errors
*/
void doWithRequest(ClientHttpRequest request) throws IOException;
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2002-2009 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.web.client.core;
import java.io.IOException;
import org.springframework.web.http.client.ClientHttpResponse;
/**
* Generic callback interface used by {@link RestTemplate}'s retrieval methods. Implementations of this interface
* perform the actual work of extracting data from a {@link ClientHttpResponse}, but don't need to worry about exception
* handling or closing resources.
*
* <p>Used internally by the {@link RestTemplate}, but also useful for application code.
*
* @author Arjen Poutsma
* @see RestTemplate#execute
* @since 3.0
*/
public interface HttpResponseExtractor<T> {
/**
* Extracts data from the given {@code ClientHttpResponse} and returns it.
*
* @param response the HTTP response
* @return the extracted data
* @throws IOException in case of I/O errors
*/
T extractData(ClientHttpResponse response) throws IOException;
}

View File

@@ -0,0 +1,206 @@
/*
* Copyright 2002-2009 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.web.client.core;
import java.net.URI;
import java.util.EnumSet;
import java.util.Map;
import org.springframework.web.http.HttpHeaders;
import org.springframework.web.http.HttpMethod;
/**
* Interface specifying a basic set of RESTful operations. Implemented by {@link RestTemplate}. Not often used directly,
* but a useful option to enhance testability, as it can easily be mocked or stubbed.
*
* @author Arjen Poutsma
* @see RestTemplate
* @since 3.0
*/
public interface RestOperations {
// GET
/**
* Retrieves a representation by doing a GET on the specified URL. URI Template variables are expanded using the
* given URI variables, if any.
*
* @param uri the URI to GET
* @param responseType the type of the return value
* @param uriVariables the variables to expand the template
* @return the converted object
*/
<T> T getForObject(String uri, Class<T> responseType, String... uriVariables);
/**
* Retrieves a representation by doing a GET on the URI template. URI Template variables are expanded using the
* given map.
*
* @param uri the URI to GET
* @param responseType the type of the return value
* @param uriVariables the map containing variables for the URI template
* @return the converted object
*/
<T> T getForObject(String uri, Class<T> responseType, Map<String, String> uriVariables);
// HEAD
/**
* Retrieves all headers of the resource specified by the URI template. URI Template variables are expanded using
* the given URI variables, if any.
*
* @param uri the URI
* @param uriVariables the variables to expand the template
* @return all HTTP headers of that resource
*/
HttpHeaders headForHeaders(String uri, String... uriVariables);
/**
* Retrieves all headers of the resource specified by the URI template. URI Template variables are expanded using
* the given map.
*
* @param uri the URI
* @param uriVariables the map containing variables for the URI template
* @return all HTTP headers of that resource
*/
HttpHeaders headForHeaders(String uri, Map<String, String> uriVariables);
// POST
/**
* Creates a new resource by POSTing the given object to the URI template. The value of the <code>Location</code>,
* indicating where the new resource is stored, is returned. URI Template variables are expanded using the given URI
* variables, if any.
*
* @param uri the URI
* @param request the Object to be POSTED
* @return the value for the <code>Location</code> header
*/
URI postForLocation(String uri, Object request, String... uriVariables);
/**
* Creates a new resource by POSTing the given object to URI template. The value of the <code>Location</code>,
* indicating where the new resource is stored, is returned. URI Template variables are expanded using the given
* map.
*
* @param uri the URI
* @param request the Object to be POSTed
* @param uriVariables the variables to expand the template
* @return the value for the <code>Location</code> header
*/
URI postForLocation(String uri, Object request, Map<String, String> uriVariables);
// PUT
/**
* Creates or updates a resource by PUTting the given object to the URI. URI Template variables are expanded using
* the given URI variables, if any.
*
* @param uri the URI
* @param request the Object to be POSTed
* @param uriVariables the variables to expand the template
*/
void put(String uri, Object request, String... uriVariables);
/**
* Creates a new resource by PUTting the given object to URI template. URI Template variables are expanded using the
* given map.
*
* @param uri the URI
* @param request the Object to be POSTed
* @param uriVariables the variables to expand the template
*/
void put(String uri, Object request, Map<String, String> uriVariables);
// DELETE
/**
* Deletes the resources at the specified URI. URI Template variables are expanded using the given URI variables, if
* any.
*
* @param uri the URI
* @param uriVariables the variables to expand in the template
*/
void delete(String uri, String... uriVariables);
/**
* Deletes the resources at the specified URI. URI Template variables are expanded using the given map.
*
* @param uri the URI
* @param uriVariables the variables to expand the template
*/
void delete(String uri, Map<String, String> uriVariables);
//OPTIONS
/**
* Returns value of the Allow header for the given URI. URI Template variables are expanded using the given URI
* variables, if any.
*
* @param uri the URI
* @param uriVariables the variables to expand in the template
* @return the value of the allow header
*/
EnumSet<HttpMethod> optionsForAllow(String uri, String... uriVariables);
/**
* Returns value of the Allow header for the given URI. URI Template variables are expanded using the given map.
*
* @param uri the URI
* @param uriVariables the variables to expand in the template
* @return the value of the allow header
*/
EnumSet<HttpMethod> optionsForAllow(String uri, Map<String, String> uriVariables);
/**
* Executes the HTTP methods to the given URI, preparing the request with the {@link HttpRequestCallback}, and
* reading the response with a {@link HttpResponseExtractor}. URI Template variables are expanded using the
* given URI variables, if any.
*
* @param uri the URI
* @param method the HTTP method (GET, POST, etc)
* @param requestCallback object that prepares the request
* @param responseExtractor object that extracts the return value from the response
* @param uriVariables the variables to expand in the template
* @return an arbitrary object, as returned by the {@link HttpResponseExtractor}
*/
<T> T execute(String uri,
HttpMethod method,
HttpRequestCallback requestCallback,
HttpResponseExtractor<T> responseExtractor,
String... uriVariables);
/**
* Executes the HTTP methods to the given URI, preparing the request with the {@link HttpRequestCallback}, and
* reading the response with a {@link HttpResponseExtractor}. URI Template variables are expanded using the
* given URI variables map.
*
* @param uri the URI
* @param method the HTTP method (GET, POST, etc)
* @param requestCallback object that prepares the request
* @param responseExtractor object that extracts the return value from the response
* @param uriVariables the variables to expand in the template
* @return an arbitrary object, as returned by the {@link HttpResponseExtractor}
*/
<T> T execute(String uri,
HttpMethod method,
HttpRequestCallback requestCallback,
HttpResponseExtractor<T> responseExtractor,
Map<String, String> uriVariables);
}

View File

@@ -0,0 +1,449 @@
/*
* Copyright 2002-2009 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.web.client.core;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.MediaType;
import org.springframework.web.client.HttpClientException;
import org.springframework.web.client.HttpIOException;
import org.springframework.web.client.support.HttpAccessor;
import org.springframework.web.converter.ByteArrayHttpMessageConverter;
import org.springframework.web.converter.HttpMessageConverter;
import org.springframework.web.converter.StringHttpMessageConverter;
import org.springframework.web.http.HttpHeaders;
import org.springframework.web.http.HttpMethod;
import org.springframework.web.http.client.ClientHttpRequest;
import org.springframework.web.http.client.ClientHttpRequestFactory;
import org.springframework.web.http.client.ClientHttpResponse;
import org.springframework.web.util.UriTemplate;
/**
* <strong>The central class for client-side HTTP access.</strong>. It simplifies communication with HTTP servers, and
* enforces RESTful principles. It handles HTTP connections, leaving application code to provide URLs (with possible
* template variables) and extract results.
*
* <p>The main entry points of this template are the methods named after the five main HTTP methods:
* <table>
* <tr><th>HTTP method<th><th>RestTemplate methods</th></tr>
* <tr><td>DELETE</td><td>{@link #delete}</td></tr>
* <tr><td>GET</td><td>{@link #getForObject}</td></tr>
* <tr><td>HEAD</td><td>{@link #headForHeaders}</td></tr>
* <tr><td>OPTIONS</td><td>{@link #optionsForAllow}</td></tr>
* <tr><td>POST</td><td>{@link #postForLocation}</td></tr>
* <tr><td>PUT</td><td>{@link #put}</td></tr>
* <tr><td>any</td><td>{@link #execute}</td></tr>
* </table>
*
* <p>Each of these methods takes {@linkplain UriTemplate uri template} arguments in two forms: as a {@code String}
* variable arguments array, or as a {@code Map<String, String>}. The string varargs variant expands the given template
* variables in order, so that
* <pre>
* String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class,"42", "21");
* </pre>
* will perform a GET on {@code http://example.com/hotels/42/bookings/21}. The map variant is explands the template
* based on variable name, and is therefore more useful when using many variables, or when a single variable is used
* multiple times. For example:
* <pre>
* Map&lt;String, String&gt; vars = Collections.singletonMap("hotel", 42);
* String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
* </pre>
* will perform a GET on {@code http://example.com/hotels/42/rooms/42}.
*
* <p>Objects passed to and returned from these methods are converted to and from HTTP messages by {@link
* HttpMessageConverter} instances. Converters for the main mime types are registered by default, but you can also write
* your own converter and register it via the {@link #setMessageConverters(HttpMessageConverter[]) messageConverters}
* bean property.
*
* <p>This template uses a {@link org.springframework.web.http.client.SimpleClientHttpRequestFactory} and a {@link
* SimpleHttpErrorHandler} as default strategies for for creating HTTP connections or handling HTTP errors, respectively.
* These defaults can be overridden through the {@link #setRequestFactory(ClientHttpRequestFactory) requestFactory} and
* {@link #setErrorHandler(HttpErrorHandler) errorHandler} bean properties.
*
* @author Arjen Poutsma
* @see HttpMessageConverter
* @see HttpRequestCallback
* @see HttpResponseExtractor
* @see HttpErrorHandler
* @since 3.0
*/
public class RestTemplate extends HttpAccessor implements RestOperations {
private final HttpResponseExtractor<HttpHeaders> headersExtractor = new HeadersExtractor();
private HttpMessageConverter<?>[] messageConverters;
private HttpErrorHandler errorHandler;
/**
* Creates a new instance of the {@link RestTemplate} using default settings.
*
* @see #initDefaultStrategies()
*/
public RestTemplate() {
initDefaultStrategies();
}
/**
* Creates a new instance of the {@link RestTemplate} based on the given {@link ClientHttpRequestFactory}.
*
* @param requestFactory HTTP request factory to use
* @see org.springframework.web.http.client.SimpleClientHttpRequestFactory
* @see org.springframework.web.http.client.commons.CommonsClientHttpRequestFactory
*/
public RestTemplate(ClientHttpRequestFactory requestFactory) {
initDefaultStrategies();
setRequestFactory(requestFactory);
}
/**
* Initializes the default stragegies for this template.
*
* <p>Default implementation sets up the {@link SimpleHttpErrorHandler} and the {@link ByteArrayHttpMessageConverter} and
* {@link StringHttpMessageConverter}.
*/
protected void initDefaultStrategies() {
errorHandler = new SimpleHttpErrorHandler();
messageConverters =
new HttpMessageConverter[]{new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter()};
}
/**
* Returns the array of message body converters. These converters are used to covert from and to HTTP requests and
* responses.
*/
public HttpMessageConverter<?>[] getMessageConverters() {
return messageConverters;
}
/**
* Returns the list of message body converters that support a particular type.
*
* @param type the type to return converters for
* @return converts that support the given type
*/
@SuppressWarnings("unchecked")
protected <T> List<HttpMessageConverter<T>> getSupportedMessageConverters(Class<T> type) {
HttpMessageConverter[] converters = getMessageConverters();
List<HttpMessageConverter<T>> result = new ArrayList<HttpMessageConverter<T>>(converters.length);
for (HttpMessageConverter converter : converters) {
if (converter.supports(type)) {
result.add((HttpMessageConverter<T>) converter);
}
}
return result;
}
/**
* Sets the array of message body converters to use. These converters are used to covert from and to HTTP requests and
* responses.
*
* <strong>Note</strong> that setting this property overrides the {@linkplain #initDefaultStrategies() default strategies}.
*/
public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) {
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
this.messageConverters = messageConverters;
}
/**
* Returns the error handler. By default, this is the {@link SimpleHttpErrorHandler}.
*/
public HttpErrorHandler getErrorHandler() {
return errorHandler;
}
/**
* Sets the error handler.
*/
public void setErrorHandler(HttpErrorHandler errorHandler) {
Assert.notNull(errorHandler, "'errorHandler' must not be null");
this.errorHandler = errorHandler;
}
// GET
public <T> T getForObject(String url, Class<T> responseType, String... urlVariables) {
checkForSupportedEntityConverter(responseType);
return execute(url, HttpMethod.GET, new GetCallback<T>(responseType),
new HttpMessageConverterExtractor<T>(responseType), urlVariables);
}
public <T> T getForObject(String url, Class<T> responseType, Map<String, String> urlVariables) {
checkForSupportedEntityConverter(responseType);
return execute(url, HttpMethod.GET, new GetCallback<T>(responseType),
new HttpMessageConverterExtractor<T>(responseType), urlVariables);
}
// POST
public URI postForLocation(String url, Object request, String... urlVariables) {
checkForSupportedEntityConverter(request.getClass());
HttpHeaders headers =
execute(url, HttpMethod.POST, new PostPutCallback(request), headersExtractor, urlVariables);
return headers.getLocation();
}
public URI postForLocation(String url, Object request, Map<String, String> urlVariables) {
checkForSupportedEntityConverter(request.getClass());
HttpHeaders headers =
execute(url, HttpMethod.POST, new PostPutCallback(request), headersExtractor, urlVariables);
return headers.getLocation();
}
// PUT
public void put(String url, Object request, String... urlVariables) {
checkForSupportedEntityConverter(request.getClass());
execute(url, HttpMethod.PUT, new PostPutCallback(request), null, urlVariables);
}
public void put(String url, Object request, Map<String, String> urlVariables) {
checkForSupportedEntityConverter(request.getClass());
execute(url, HttpMethod.PUT, new PostPutCallback(request), null, urlVariables);
}
// HEAD
public HttpHeaders headForHeaders(String url, String... urlVariables) {
return execute(url, HttpMethod.HEAD, null, headersExtractor, urlVariables);
}
public HttpHeaders headForHeaders(String url, Map<String, String> urlVariables) {
return execute(url, HttpMethod.HEAD, null, headersExtractor, urlVariables);
}
// DELETE
public void delete(String url, String... urlVariables) {
execute(url, HttpMethod.DELETE, null, null, urlVariables);
}
public void delete(String url, Map<String, String> urlVariables) {
execute(url, HttpMethod.DELETE, null, null, urlVariables);
}
// OPTIONS
public EnumSet<HttpMethod> optionsForAllow(String url, String... urlVariables) {
HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, headersExtractor, urlVariables);
return headers.getAllow();
}
public EnumSet<HttpMethod> optionsForAllow(String url, Map<String, String> urlVariables) {
HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, headersExtractor, urlVariables);
return headers.getAllow();
}
// execute
public <T> T execute(String url,
HttpMethod method,
HttpRequestCallback requestCallback,
HttpResponseExtractor<T> responseExtractor,
String... urlVariables) {
UriTemplate uriTemplate = new UriTemplate(url);
URI expanded = uriTemplate.expand(urlVariables);
return doExecute(expanded, method, requestCallback, responseExtractor);
}
public <T> T execute(String url,
HttpMethod method,
HttpRequestCallback requestCallback,
HttpResponseExtractor<T> responseExtractor,
Map<String, String> urlVariables) {
UriTemplate uriTemplate = new UriTemplate(url);
URI expanded = uriTemplate.expand(urlVariables);
return doExecute(expanded, method, requestCallback, responseExtractor);
}
/**
* Execute the given method on the provided URI. The {@link ClientHttpRequest} is processed using the {@link
* HttpRequestCallback}; the response with the {@link HttpResponseExtractor}.
*
* @param url the fully-expanded URL to connect to
* @param method the HTTP method to execute (GET, POST, etc.)
* @param requestCallback object that prepares the request. Can be <code>null</code>.
* @param responseExtractor object that extracts the return value from the response. Can be <code>null</code>.
* @return an arbitrary object, as returned by the {@link HttpResponseExtractor}
*/
protected <T> T doExecute(URI url,
HttpMethod method,
HttpRequestCallback requestCallback,
HttpResponseExtractor<T> responseExtractor) {
Assert.notNull(url, "'url' must not be null");
Assert.notNull(method, "'method' must not be null");
ClientHttpResponse response = null;
try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
if (getErrorHandler().hasError(response)) {
getErrorHandler().handleError(response);
}
if (responseExtractor != null) {
return responseExtractor.extractData(response);
}
else {
return null;
}
}
catch (IOException ex) {
throw new HttpIOException("I/O error: " + ex.getMessage(), ex);
}
finally {
if (response != null) {
response.close();
}
}
}
/**
* Checks whether any of the registered {@linkplain #setMessageConverters(HttpMessageConverter[]) message body
* converters} can convert the given type.
*
* @param type the type to check for
* @throws IllegalArgumentException if no supported entity converter can be found
* @see HttpMessageConverter#supports(Class)
*/
private void checkForSupportedEntityConverter(Class type) {
for (HttpMessageConverter<?> entityConverter : getMessageConverters()) {
if (entityConverter.supports(type)) {
return;
}
}
throw new IllegalArgumentException("Could not resolve HttpMessageConverter for [" + type.getName() + "]");
}
/**
* Request callback implementation that sets the <code>Accept</code> header based on the registered {@linkplain
* HttpMessageConverter entity converters}.
*/
private class AcceptHeaderCallback implements HttpRequestCallback {
public void doWithRequest(ClientHttpRequest request) throws IOException {
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
for (HttpMessageConverter<?> entityConverter : getMessageConverters()) {
List<MediaType> supportedMediaTypes = entityConverter.getSupportedMediaTypes();
for (MediaType supportedMediaType : supportedMediaTypes) {
if (supportedMediaType.getCharSet() != null) {
supportedMediaType =
new MediaType(supportedMediaType.getType(), supportedMediaType.getSubtype());
}
allSupportedMediaTypes.add(supportedMediaType);
}
}
Collections.sort(allSupportedMediaTypes);
request.getHeaders().setAccept(allSupportedMediaTypes);
}
}
private class GetCallback<T> implements HttpRequestCallback {
private final Class<T> responseType;
private GetCallback(Class<T> responseType) {
this.responseType = responseType;
}
public void doWithRequest(ClientHttpRequest request) throws IOException {
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
for (HttpMessageConverter<?> entityConverter : getSupportedMessageConverters(responseType)) {
List<MediaType> supportedMediaTypes = entityConverter.getSupportedMediaTypes();
for (MediaType supportedMediaType : supportedMediaTypes) {
if (supportedMediaType.getCharSet() != null) {
supportedMediaType =
new MediaType(supportedMediaType.getType(), supportedMediaType.getSubtype());
}
allSupportedMediaTypes.add(supportedMediaType);
}
}
Collections.sort(allSupportedMediaTypes);
request.getHeaders().setAccept(allSupportedMediaTypes);
}
}
/**
* Extension of {@link AcceptHeaderCallback} that writes the given object to the request stream.
*/
private class PostPutCallback implements HttpRequestCallback {
private final Object request;
private PostPutCallback(Object request) {
this.request = request;
}
@SuppressWarnings("unchecked")
public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
for (HttpMessageConverter entityConverter : getSupportedMessageConverters(request.getClass())) {
entityConverter.write(request, httpRequest);
break;
}
}
}
/**
* Response extractor that uses the registered {@linkplain HttpMessageConverter entity converters} to convert the
* response into a type <code>T</code>.
*/
private class HttpMessageConverterExtractor<T> implements HttpResponseExtractor<T> {
private final Class<T> responseType;
private HttpMessageConverterExtractor(Class<T> responseType) {
this.responseType = responseType;
}
public T extractData(ClientHttpResponse response) throws IOException {
MediaType contentType = response.getHeaders().getContentType();
if (contentType == null) {
throw new HttpClientException("Cannot extract response: no Content-Type found");
}
for (HttpMessageConverter<T> messageConverter : getSupportedMessageConverters(responseType)) {
for (MediaType supportedMediaType : messageConverter.getSupportedMediaTypes()) {
if (supportedMediaType.includes(contentType)) {
return messageConverter.read(responseType, response);
}
}
}
throw new HttpClientException(
"Could not extract response: no suitable HttpMessageConverter found for " + "response type [" +
responseType.getName() + "] and content type [" + contentType + "]");
}
}
/**
* Response extractor that extracts the response {@link HttpHeaders}.
*/
private static class HeadersExtractor implements HttpResponseExtractor<HttpHeaders> {
public HttpHeaders extractData(ClientHttpResponse response) throws IOException {
return response.getHeaders();
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2002-2009 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.web.client.core;
import java.io.IOException;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpClientException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.http.HttpStatus;
import org.springframework.web.http.client.ClientHttpResponse;
/**
* Default implementation of the {@link HttpErrorHandler} interface.
*
* <p>This error handler checks for the status code on the {@link ClientHttpResponse}: any code with series
* {@link HttpStatus.Series#CLIENT_ERROR} or {@link HttpStatus.Series#SERVER_ERROR} is considered to be an error.
* This behavior can be changed by overriding the {@link #hasError(HttpStatus)} method.
*
* @author Arjen Poutsma
* @see RestTemplate#setErrorHandler(HttpErrorHandler)
* @since 3.0
*/
public class SimpleHttpErrorHandler implements HttpErrorHandler {
/**
* Delegates to {@link #hasError(HttpStatus)} with the response status code.
*/
public boolean hasError(ClientHttpResponse response) throws IOException {
return hasError(response.getStatusCode());
}
/**
* Template method called from {@link #hasError(ClientHttpResponse)}.
*
* <p>Default implementation checks if the given status code is {@link HttpStatus.Series#CLIENT_ERROR} or {@link
* HttpStatus.Series#SERVER_ERROR}. Can be overridden in subclasses.
*
* @param statusCode the HTTP status code
* @return <code>true</code> if the response has an error; <code>false</code> otherwise
* @see HttpStatus.Series#CLIENT_ERROR
* @see HttpStatus.Series#SERVER_ERROR
*/
protected boolean hasError(HttpStatus statusCode) {
return statusCode.series() == HttpStatus.Series.CLIENT_ERROR ||
statusCode.series() == HttpStatus.Series.SERVER_ERROR;
}
public void handleError(ClientHttpResponse response) throws IOException {
HttpStatus statusCode = response.getStatusCode();
switch (statusCode.series()) {
case CLIENT_ERROR:
throw new HttpClientErrorException(statusCode, response.getStatusText());
case SERVER_ERROR:
throw new HttpServerErrorException(statusCode, response.getStatusText());
default:
throw new HttpClientException("Unknown status code [" + statusCode + "]");
}
}
}

View File

@@ -0,0 +1,8 @@
<html>
<body>
Core package of the client-side HTTP support.
Provides a RestTemplate class and various callback interfaces.
</body>
</html>

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2002-2009 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.web.client.core.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.web.client.core.RestTemplate;
import org.springframework.web.http.client.ClientHttpRequestFactory;
/**
* Convenient super class for application classes that need REST access.
*
* <p>Requires a {@link ClientHttpRequestFactory} or a {@link RestTemplate} instance to be set. It will create its own
* JmsTemplate if a ConnectionFactory is passed in. A custom JmsTemplate instance can be created for a given
* ConnectionFactory through overriding the <code>createJmsTemplate</code> method.
*
* @author Arjen Poutsma
* @see #setRestTemplate(RestTemplate)
* @see RestTemplate
* @since 3.0
*/
public class RestGatewaySupport {
/**
* Logger available to subclasses.
*/
protected final Log logger = LogFactory.getLog(getClass());
private RestTemplate restTemplate;
/**
* Constructs a new instance of the {@link RestGatewaySupport}, with default parameters.
*
* @see RestTemplate#RestTemplate()
*/
public RestGatewaySupport() {
restTemplate = new RestTemplate();
}
/**
* Constructs a new instance of the {@link RestGatewaySupport}, with the given {@link ClientHttpRequestFactory}.
*
* @see RestTemplate#RestTemplate(ClientHttpRequestFactory
*/
public RestGatewaySupport(ClientHttpRequestFactory requestFactory) {
Assert.notNull(requestFactory, "'requestFactory' must not be null");
this.restTemplate = new RestTemplate(requestFactory);
}
/**
* Returns the {@link RestTemplate} for the gateway.
*/
public RestTemplate getRestTemplate() {
return restTemplate;
}
/**
* Sets the {@link RestTemplate} for the gateway.
*/
public void setRestTemplate(RestTemplate restTemplate) {
Assert.notNull(restTemplate, "'restTemplate' must not be null");
this.restTemplate = restTemplate;
}
}

View File

@@ -0,0 +1,8 @@
<html>
<body>
Classes supporting the org.springframework.web.client.core package.
Contains a base class for RestTemplate usage.
</body>
</html>

View File

@@ -0,0 +1,7 @@
<html>
<body>
This package contains integration classes for client-side access of HTTP services.
</body>
</html>

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2002-2009 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.web.client.support;
import java.io.IOException;
import java.net.URI;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.web.http.HttpMethod;
import org.springframework.web.http.client.ClientHttpRequest;
import org.springframework.web.http.client.ClientHttpRequestFactory;
import org.springframework.web.http.client.SimpleClientHttpRequestFactory;
/**
* Base class for {@link org.springframework.web.client.core.RestTemplate} and other HTTP accessing gateway helpers, defining
* common properties such as the {@link ClientHttpRequestFactory} to operate on.
* <p/>
* Not intended to be used directly. See {@link org.springframework.web.client.core.RestTemplate}.
*
* @author Arjen Poutsma
* @see org.springframework.web.client.core.RestTemplate
*/
public abstract class HttpAccessor {
/**
* Logger available to subclasses.
*/
protected final Log logger = LogFactory.getLog(getClass());
private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
/**
* Returns the request factory that this accessor uses for obtaining {@link ClientHttpRequest HttpRequests}
*/
public ClientHttpRequestFactory getRequestFactory() {
return requestFactory;
}
/**
* Sets the request factory that this accessor uses for obtaining {@link ClientHttpRequest HttpRequests}
*/
public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
Assert.notNull(requestFactory, "'requestFactory' must not be null");
this.requestFactory = requestFactory;
}
/**
* Creates a new {@link ClientHttpRequest} via this template's {@link ClientHttpRequestFactory}.
*
* @param url the URL to connect to
* @param method the HTTP method to exectute (GET, POST, etc.)
* @return the created request
* @throws IOException in case of I/O errors
*/
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
ClientHttpRequest request = getRequestFactory().createRequest(url, method);
if (logger.isDebugEnabled()) {
logger.debug("Created " + method.name() + " request for \"" + url + "\"");
}
return request;
}
}

View File

@@ -0,0 +1,8 @@
<html>
<body>
This package provides generic HTTP support classes,
to be used by higher-level classes like RestTemplate.
</body>
</html>