diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/HttpClientErrorException.java b/org.springframework.web/src/main/java/org/springframework/web/client/HttpClientErrorException.java
new file mode 100644
index 0000000000..6549bf820f
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/HttpClientErrorException.java
@@ -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);
+ }
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/HttpClientException.java b/org.springframework.web/src/main/java/org/springframework/web/client/HttpClientException.java
new file mode 100644
index 0000000000..89f4e0d3ba
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/HttpClientException.java
@@ -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);
+ }
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/HttpIOException.java b/org.springframework.web/src/main/java/org/springframework/web/client/HttpIOException.java
new file mode 100644
index 0000000000..865078cbf1
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/HttpIOException.java
@@ -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);
+ }
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/HttpServerErrorException.java b/org.springframework.web/src/main/java/org/springframework/web/client/HttpServerErrorException.java
new file mode 100644
index 0000000000..62e5f6f8fc
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/HttpServerErrorException.java
@@ -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);
+ }
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/HttpStatusCodeException.java b/org.springframework.web/src/main/java/org/springframework/web/client/HttpStatusCodeException.java
new file mode 100644
index 0000000000..249ff795e8
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/HttpStatusCodeException.java
@@ -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;
+ }
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/core/HttpErrorHandler.java b/org.springframework.web/src/main/java/org/springframework/web/client/core/HttpErrorHandler.java
new file mode 100644
index 0000000000..d7550e9aad
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/core/HttpErrorHandler.java
@@ -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 true if the response has an error; false 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 true.
+ *
+ * @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;
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/core/HttpRequestCallback.java b/org.springframework.web/src/main/java/org/springframework/web/client/core/HttpRequestCallback.java
new file mode 100644
index 0000000000..51c141e1d1
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/core/HttpRequestCallback.java
@@ -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.
+ *
+ *
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;
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/core/HttpResponseExtractor.java b/org.springframework.web/src/main/java/org/springframework/web/client/core/HttpResponseExtractor.java
new file mode 100644
index 0000000000..29b2b57bd1
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/core/HttpResponseExtractor.java
@@ -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.
+ *
+ *
Used internally by the {@link RestTemplate}, but also useful for application code.
+ *
+ * @author Arjen Poutsma
+ * @see RestTemplate#execute
+ * @since 3.0
+ */
+public interface HttpResponseExtractor {
+
+ /**
+ * 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;
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/core/RestOperations.java b/org.springframework.web/src/main/java/org/springframework/web/client/core/RestOperations.java
new file mode 100644
index 0000000000..52bba14cc2
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/core/RestOperations.java
@@ -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 getForObject(String uri, Class 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 getForObject(String uri, Class responseType, Map 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 uriVariables);
+
+ // POST
+
+ /**
+ * Creates a new resource by POSTing the given object to the URI template. The value of the Location,
+ * 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 Location 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 Location,
+ * 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 Location header
+ */
+ URI postForLocation(String uri, Object request, Map 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 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 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 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 optionsForAllow(String uri, Map 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 execute(String uri,
+ HttpMethod method,
+ HttpRequestCallback requestCallback,
+ HttpResponseExtractor 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 execute(String uri,
+ HttpMethod method,
+ HttpRequestCallback requestCallback,
+ HttpResponseExtractor responseExtractor,
+ Map uriVariables);
+
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/core/RestTemplate.java b/org.springframework.web/src/main/java/org/springframework/web/client/core/RestTemplate.java
new file mode 100644
index 0000000000..09a56956e3
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/core/RestTemplate.java
@@ -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;
+
+/**
+ * The central class for client-side HTTP access.. 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.
+ *
+ * The main entry points of this template are the methods named after the five main HTTP methods:
+ *
+ * | HTTP method | | RestTemplate methods |
+ * | DELETE | {@link #delete} |
+ * | GET | {@link #getForObject} |
+ * | HEAD | {@link #headForHeaders} |
+ * | OPTIONS | {@link #optionsForAllow} |
+ * | POST | {@link #postForLocation} |
+ * | PUT | {@link #put} |
+ * | any | {@link #execute} |
+ *
+ *
+ * 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}. The string varargs variant expands the given template
+ * variables in order, so that
+ *
+ * String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class,"42", "21");
+ *
+ * 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:
+ *
+ * Map<String, String> vars = Collections.singletonMap("hotel", 42);
+ * String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
+ *
+ * will perform a GET on {@code http://example.com/hotels/42/rooms/42}.
+ *
+ * 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.
+ *
+ *
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 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.
+ *
+ * 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 List> getSupportedMessageConverters(Class type) {
+ HttpMessageConverter[] converters = getMessageConverters();
+ List> result = new ArrayList>(converters.length);
+ for (HttpMessageConverter converter : converters) {
+ if (converter.supports(type)) {
+ result.add((HttpMessageConverter) 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.
+ *
+ * Note 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 getForObject(String url, Class responseType, String... urlVariables) {
+ checkForSupportedEntityConverter(responseType);
+ return execute(url, HttpMethod.GET, new GetCallback(responseType),
+ new HttpMessageConverterExtractor(responseType), urlVariables);
+ }
+
+ public T getForObject(String url, Class responseType, Map urlVariables) {
+ checkForSupportedEntityConverter(responseType);
+ return execute(url, HttpMethod.GET, new GetCallback(responseType),
+ new HttpMessageConverterExtractor(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 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 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 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 urlVariables) {
+ execute(url, HttpMethod.DELETE, null, null, urlVariables);
+ }
+
+ // OPTIONS
+
+ public EnumSet optionsForAllow(String url, String... urlVariables) {
+ HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, headersExtractor, urlVariables);
+ return headers.getAllow();
+ }
+
+ public EnumSet optionsForAllow(String url, Map urlVariables) {
+ HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, headersExtractor, urlVariables);
+ return headers.getAllow();
+ }
+
+ // execute
+
+ public T execute(String url,
+ HttpMethod method,
+ HttpRequestCallback requestCallback,
+ HttpResponseExtractor responseExtractor,
+ String... urlVariables) {
+ UriTemplate uriTemplate = new UriTemplate(url);
+ URI expanded = uriTemplate.expand(urlVariables);
+ return doExecute(expanded, method, requestCallback, responseExtractor);
+ }
+
+ public T execute(String url,
+ HttpMethod method,
+ HttpRequestCallback requestCallback,
+ HttpResponseExtractor responseExtractor,
+ Map 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 null.
+ * @param responseExtractor object that extracts the return value from the response. Can be null.
+ * @return an arbitrary object, as returned by the {@link HttpResponseExtractor}
+ */
+ protected T doExecute(URI url,
+ HttpMethod method,
+ HttpRequestCallback requestCallback,
+ HttpResponseExtractor 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 Accept header based on the registered {@linkplain
+ * HttpMessageConverter entity converters}.
+ */
+ private class AcceptHeaderCallback implements HttpRequestCallback {
+
+ public void doWithRequest(ClientHttpRequest request) throws IOException {
+ List allSupportedMediaTypes = new ArrayList();
+ for (HttpMessageConverter> entityConverter : getMessageConverters()) {
+ List 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 implements HttpRequestCallback {
+
+ private final Class responseType;
+
+ private GetCallback(Class responseType) {
+ this.responseType = responseType;
+ }
+
+ public void doWithRequest(ClientHttpRequest request) throws IOException {
+ List allSupportedMediaTypes = new ArrayList();
+ for (HttpMessageConverter> entityConverter : getSupportedMessageConverters(responseType)) {
+ List 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 T.
+ */
+ private class HttpMessageConverterExtractor implements HttpResponseExtractor {
+
+ private final Class responseType;
+
+ private HttpMessageConverterExtractor(Class 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 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 {
+
+ public HttpHeaders extractData(ClientHttpResponse response) throws IOException {
+ return response.getHeaders();
+ }
+ }
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/core/SimpleHttpErrorHandler.java b/org.springframework.web/src/main/java/org/springframework/web/client/core/SimpleHttpErrorHandler.java
new file mode 100644
index 0000000000..5b69f97b37
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/core/SimpleHttpErrorHandler.java
@@ -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.
+ *
+ * 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)}.
+ *
+ *
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 true if the response has an error; false 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 + "]");
+ }
+ }
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/core/package.html b/org.springframework.web/src/main/java/org/springframework/web/client/core/package.html
new file mode 100644
index 0000000000..fe274ba0f5
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/core/package.html
@@ -0,0 +1,8 @@
+
+
+
+Core package of the client-side HTTP support.
+Provides a RestTemplate class and various callback interfaces.
+
+
+
diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/core/support/RestGatewaySupport.java b/org.springframework.web/src/main/java/org/springframework/web/client/core/support/RestGatewaySupport.java
new file mode 100644
index 0000000000..0812970f1d
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/core/support/RestGatewaySupport.java
@@ -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.
+ *
+ * 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 createJmsTemplate 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;
+ }
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/core/support/package.html b/org.springframework.web/src/main/java/org/springframework/web/client/core/support/package.html
new file mode 100644
index 0000000000..9a319aa7d6
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/core/support/package.html
@@ -0,0 +1,8 @@
+
+
+
+Classes supporting the org.springframework.web.client.core package.
+Contains a base class for RestTemplate usage.
+
+
+
diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/package.html b/org.springframework.web/src/main/java/org/springframework/web/client/package.html
new file mode 100644
index 0000000000..094b557c35
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/package.html
@@ -0,0 +1,7 @@
+
+
+
+This package contains integration classes for client-side access of HTTP services.
+
+
+
diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/support/HttpAccessor.java b/org.springframework.web/src/main/java/org/springframework/web/client/support/HttpAccessor.java
new file mode 100644
index 0000000000..e3e868d352
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/support/HttpAccessor.java
@@ -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.
+ *
+ * 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;
+ }
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/support/package.html b/org.springframework.web/src/main/java/org/springframework/web/client/support/package.html
new file mode 100644
index 0000000000..b6dbef257a
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/client/support/package.html
@@ -0,0 +1,8 @@
+
+
+
+This package provides generic HTTP support classes,
+to be used by higher-level classes like RestTemplate.
+
+
+
diff --git a/org.springframework.web/src/test/java/org/springframework/web/client/core/RestTemplateIntegrationTests.java b/org.springframework.web/src/test/java/org/springframework/web/client/core/RestTemplateIntegrationTests.java
new file mode 100644
index 0000000000..52fb2816e1
--- /dev/null
+++ b/org.springframework.web/src/test/java/org/springframework/web/client/core/RestTemplateIntegrationTests.java
@@ -0,0 +1,171 @@
+/*
+ * 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.net.URISyntaxException;
+import java.util.EnumSet;
+import javax.servlet.GenericServlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.AfterClass;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.servlet.Context;
+import org.mortbay.jetty.servlet.ServletHolder;
+
+import org.springframework.util.FileCopyUtils;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.HttpServerErrorException;
+import org.springframework.web.http.HttpMethod;
+import org.springframework.web.http.client.commons.CommonsClientHttpRequestFactory;
+
+/**
+ * @author Arjen Poutsma
+ */
+public class RestTemplateIntegrationTests {
+
+ private RestTemplate template;
+
+ private static Server jettyServer;
+
+ @BeforeClass
+ public static void startJettyServer() throws Exception {
+ jettyServer = new Server(8889);
+ Context jettyContext = new Context(jettyServer, "/");
+ String s = "H\u00e9llo W\u00f6rld";
+ byte[] bytes = s.getBytes("UTF-8");
+ jettyContext.addServlet(new ServletHolder(new GetServlet(bytes, "text/plain;charset=utf-8")), "/get");
+ jettyContext
+ .addServlet(new ServletHolder(new PostServlet(s, new URI("http://localhost:8889/post/1"))), "/post");
+ jettyContext.addServlet(new ServletHolder(new ErrorServlet(404)), "/errors/notfound");
+ jettyContext.addServlet(new ServletHolder(new ErrorServlet(500)), "/errors/server");
+ jettyServer.start();
+ }
+
+ @Before
+ public void createTemplate() {
+// template = new RestTemplate();
+ template = new RestTemplate(new CommonsClientHttpRequestFactory());
+ }
+
+ @AfterClass
+ public static void stopJettyServer() throws Exception {
+ if (jettyServer != null) {
+ jettyServer.stop();
+ }
+ }
+
+ @Test
+ public void getString() {
+ String s = template.getForObject("http://localhost:8889/{method}", String.class, "get");
+ assertEquals("Invalid content", "H\u00e9llo W\u00f6rld", s);
+ }
+
+ @Test
+ public void postString() throws URISyntaxException {
+ URI location = template.postForLocation("http://localhost:8889/{method}", "H\u00e9llo W\u00f6rld", "post");
+ assertEquals("Invalid location", new URI("http://localhost:8889/post/1"), location);
+ }
+
+ @Test(expected = HttpClientErrorException.class)
+ public void notFound() {
+ template.execute("http://localhost:8889/errors/notfound", HttpMethod.GET, null, null);
+ }
+
+ @Test(expected = HttpServerErrorException.class)
+ public void serverError() {
+ template.execute("http://localhost:8889/errors/server", HttpMethod.GET, null, null);
+ }
+
+ @Test
+ public void optionsForAllow() {
+ EnumSet allowed = template.optionsForAllow("http://localhost:8889/get");
+ assertEquals("Invalid response",
+ EnumSet.of(HttpMethod.GET, HttpMethod.OPTIONS, HttpMethod.HEAD, HttpMethod.TRACE), allowed);
+ }
+
+ /**
+ * Servlet that returns and error message for a given status code.
+ */
+ private static class ErrorServlet extends GenericServlet {
+
+ private final int sc;
+
+ private ErrorServlet(int sc) {
+ this.sc = sc;
+ }
+
+ @Override
+ public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
+ ((HttpServletResponse) response).sendError(sc);
+ }
+ }
+
+ private static class GetServlet extends HttpServlet {
+
+ private final byte[] buf;
+
+ private final String contentType;
+
+ private GetServlet(byte[] buf, String contentType) {
+ this.buf = buf;
+ this.contentType = contentType;
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ response.setContentLength(buf.length);
+ response.setContentType(contentType);
+ FileCopyUtils.copy(buf, response.getOutputStream());
+ }
+ }
+
+ private static class PostServlet extends HttpServlet {
+
+ private final String s;
+
+ private final URI location;
+
+ private PostServlet(String s, URI location) {
+ this.s = s;
+ this.location = location;
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ assertTrue("Invalid request content-length", request.getContentLength() > 0);
+ assertNotNull("No content-type", request.getContentType());
+ String body = FileCopyUtils.copyToString(request.getReader());
+ assertEquals("Invalid request body", s, body);
+ response.setStatus(HttpServletResponse.SC_CREATED);
+ response.setHeader("Location", location.toASCIIString());
+ }
+ }
+
+}
diff --git a/org.springframework.web/src/test/java/org/springframework/web/client/core/RestTemplateTest.java b/org.springframework.web/src/test/java/org/springframework/web/client/core/RestTemplateTest.java
new file mode 100644
index 0000000000..a6a321bdfd
--- /dev/null
+++ b/org.springframework.web/src/test/java/org/springframework/web/client/core/RestTemplateTest.java
@@ -0,0 +1,349 @@
+/*
+ * 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.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.util.MediaType;
+import org.springframework.web.client.HttpClientException;
+import org.springframework.web.client.HttpIOException;
+import org.springframework.web.client.HttpServerErrorException;
+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.HttpStatus;
+import org.springframework.web.http.client.ClientHttpRequest;
+import org.springframework.web.http.client.ClientHttpRequestFactory;
+import org.springframework.web.http.client.ClientHttpResponse;
+
+/**
+ * @author Arjen Poutsma
+ */
+@SuppressWarnings("unchecked")
+public class RestTemplateTest {
+
+ private RestTemplate template;
+
+ private ClientHttpRequestFactory requestFactory;
+
+ private ClientHttpRequest request;
+
+ private ClientHttpResponse response;
+
+ private HttpErrorHandler errorHandler;
+
+ private HttpMessageConverter converter;
+
+ @Before
+ public void setUp() {
+ requestFactory = createMock(ClientHttpRequestFactory.class);
+ request = createMock(ClientHttpRequest.class);
+ response = createMock(ClientHttpResponse.class);
+ errorHandler = createMock(HttpErrorHandler.class);
+ converter = createMock(HttpMessageConverter.class);
+ template = new RestTemplate(requestFactory);
+ template.setErrorHandler(errorHandler);
+ template.setMessageConverters(new HttpMessageConverter>[]{converter});
+ }
+
+ @Test
+ public void getSupportedMessageBodyConverters() {
+ ByteArrayHttpMessageConverter byteArrayConverter = new ByteArrayHttpMessageConverter();
+ StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
+ template.setMessageConverters(new HttpMessageConverter>[]{byteArrayConverter, stringConverter});
+
+ List> result = template.getSupportedMessageConverters(String.class);
+ assertEquals("Invalid amount of String converters", 1, result.size());
+ assertEquals("Invalid String converters", stringConverter, result.get(0));
+ }
+
+ @Test
+ public void varArgsTemplateVariables() throws Exception {
+ expect(requestFactory.createRequest(new URI("http://example.com/hotels/42/bookings/21"), HttpMethod.GET))
+ .andReturn(request);
+ expect(request.execute()).andReturn(response);
+ expect(errorHandler.hasError(response)).andReturn(false);
+ response.close();
+
+ replayMocks();
+
+ template.execute("http://example.com/hotels/{hotel}/bookings/{booking}", HttpMethod.GET, null, null, "42",
+ "21");
+
+ verifyMocks();
+ }
+
+ @Test
+ public void mapTemplateVariables() throws Exception {
+ expect(requestFactory.createRequest(new URI("http://example.com/hotels/42/bookings/42"), HttpMethod.GET))
+ .andReturn(request);
+ expect(request.execute()).andReturn(response);
+ expect(errorHandler.hasError(response)).andReturn(false);
+ response.close();
+
+ replayMocks();
+
+ Map vars = Collections.singletonMap("hotel", "42");
+ template.execute("http://example.com/hotels/{hotel}/bookings/{hotel}", HttpMethod.GET, null, null, vars);
+
+ verifyMocks();
+ }
+
+ @Test
+ public void errorHandling() throws Exception {
+ expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.GET)).andReturn(request);
+ expect(request.execute()).andReturn(response);
+ expect(errorHandler.hasError(response)).andReturn(true);
+ errorHandler.handleError(response);
+ expectLastCall().andThrow(new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR));
+ response.close();
+
+ replayMocks();
+
+ try {
+ template.execute("http://example.com", HttpMethod.GET, null, null);
+ fail("HttpServerErrorException expected");
+ }
+ catch (HttpServerErrorException ex) {
+ // expected
+ }
+ verifyMocks();
+ }
+
+ @Test
+ public void getForObject() throws Exception {
+ expect(converter.supports(String.class)).andReturn(true).times(3);
+ MediaType textPlain = new MediaType("text", "plain");
+ expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(textPlain)).times(2);
+ expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.GET)).andReturn(request);
+ HttpHeaders requestHeaders = new HttpHeaders();
+ expect(request.getHeaders()).andReturn(requestHeaders);
+ expect(request.execute()).andReturn(response);
+ expect(errorHandler.hasError(response)).andReturn(false);
+ HttpHeaders responseHeaders = new HttpHeaders();
+ responseHeaders.setContentType(textPlain);
+ expect(response.getHeaders()).andReturn(responseHeaders);
+ String expected = "Hello World";
+ expect(converter.read(String.class, response)).andReturn(expected);
+ response.close();
+
+ replayMocks();
+
+ String result = template.getForObject("http://example.com", String.class);
+ assertEquals("Invalid GET result", expected, result);
+
+ verifyMocks();
+ }
+
+ @Test
+ public void getForObjectUnsupportedClass() throws Exception {
+ expect(converter.supports(String.class)).andReturn(false);
+
+ replayMocks();
+
+ try {
+ template.getForObject("http://example.com/{p}", String.class, "resource");
+ fail("IllegalArgumentException expected");
+ }
+ catch (IllegalArgumentException ex) {
+ // expected
+ }
+
+ verifyMocks();
+ }
+
+ @Test
+ public void getUnsupportedMediaType() throws Exception {
+ expect(converter.supports(String.class)).andReturn(true).times(3);
+ MediaType supportedMediaType = new MediaType("foo", "bar");
+ expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(supportedMediaType)).times(2);
+ expect(requestFactory.createRequest(new URI("http://example.com/resource"), HttpMethod.GET)).andReturn(request);
+ HttpHeaders requestHeaders = new HttpHeaders();
+ expect(request.getHeaders()).andReturn(requestHeaders);
+ expect(request.execute()).andReturn(response);
+ expect(errorHandler.hasError(response)).andReturn(false);
+ HttpHeaders responseHeaders = new HttpHeaders();
+ MediaType contentType = new MediaType("bar", "baz");
+ responseHeaders.setContentType(contentType);
+ expect(response.getHeaders()).andReturn(responseHeaders);
+ response.close();
+
+ replayMocks();
+
+ try {
+ template.getForObject("http://example.com/{p}", String.class, "resource");
+ fail("UnsupportedMediaTypeException expected");
+ }
+ catch (HttpClientException ex) {
+ // expected
+ }
+ verifyMocks();
+ }
+
+ @Test
+ public void headForHeaders() throws Exception {
+ expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.HEAD)).andReturn(request);
+ expect(request.execute()).andReturn(response);
+ expect(errorHandler.hasError(response)).andReturn(false);
+ HttpHeaders responseHeaders = new HttpHeaders();
+ expect(response.getHeaders()).andReturn(responseHeaders);
+ response.close();
+
+ replayMocks();
+ HttpHeaders result = template.headForHeaders("http://example.com");
+
+ assertSame("Invalid headers returned", responseHeaders, result);
+
+ verifyMocks();
+ }
+
+ @Test
+ public void postForLocation() throws Exception {
+ expect(converter.supports(String.class)).andReturn(true).times(2);
+ expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).andReturn(request);
+ String helloWorld = "Hello World";
+ converter.write(helloWorld, request);
+ expect(request.execute()).andReturn(response);
+ expect(errorHandler.hasError(response)).andReturn(false);
+ HttpHeaders responseHeaders = new HttpHeaders();
+ URI expected = new URI("http://example.com/hotels");
+ responseHeaders.setLocation(expected);
+ expect(response.getHeaders()).andReturn(responseHeaders);
+ response.close();
+
+ replayMocks();
+
+ URI result = template.postForLocation("http://example.com", helloWorld);
+ assertEquals("Invalid POST result", expected, result);
+
+ verifyMocks();
+ }
+
+ @Test
+ public void postForLocationNoLocation() throws Exception {
+ expect(converter.supports(String.class)).andReturn(true).times(2);
+ expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).andReturn(request);
+ String helloWorld = "Hello World";
+ converter.write(helloWorld, request);
+ expect(request.execute()).andReturn(response);
+ expect(errorHandler.hasError(response)).andReturn(false);
+ HttpHeaders responseHeaders = new HttpHeaders();
+ expect(response.getHeaders()).andReturn(responseHeaders);
+ response.close();
+
+ replayMocks();
+
+ URI result = template.postForLocation("http://example.com", helloWorld);
+ assertNull("Invalid POST result", result);
+
+ verifyMocks();
+ }
+
+ @Test
+ public void put() throws Exception {
+ expect(converter.supports(String.class)).andReturn(true).times(2);
+ expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.PUT)).andReturn(request);
+ String helloWorld = "Hello World";
+ converter.write(helloWorld, request);
+ expect(request.execute()).andReturn(response);
+ expect(errorHandler.hasError(response)).andReturn(false);
+ response.close();
+
+ replayMocks();
+
+ template.put("http://example.com", helloWorld);
+
+ verifyMocks();
+ }
+
+ @Test
+ public void delete() throws Exception {
+ expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.DELETE)).andReturn(request);
+ expect(request.execute()).andReturn(response);
+ expect(errorHandler.hasError(response)).andReturn(false);
+ response.close();
+
+ replayMocks();
+
+ template.delete("http://example.com");
+
+ verifyMocks();
+ }
+
+ @Test
+ public void optionsForAllow() throws Exception {
+ expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.OPTIONS)).andReturn(request);
+ expect(request.execute()).andReturn(response);
+ expect(errorHandler.hasError(response)).andReturn(false);
+ HttpHeaders responseHeaders = new HttpHeaders();
+ EnumSet expected = EnumSet.of(HttpMethod.GET, HttpMethod.POST);
+ responseHeaders.setAllow(expected);
+ expect(response.getHeaders()).andReturn(responseHeaders);
+ response.close();
+
+ replayMocks();
+
+ EnumSet result = template.optionsForAllow("http://example.com");
+ assertEquals("Invalid OPTIONS result", expected, result);
+
+ verifyMocks();
+ }
+
+ @Test
+ public void ioException() throws Exception {
+ expect(converter.supports(String.class)).andReturn(true).times(2);
+ MediaType mediaType = new MediaType("foo", "bar");
+ expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(mediaType));
+ expect(requestFactory.createRequest(new URI("http://example.com/resource"), HttpMethod.GET)).andReturn(request);
+ expect(request.getHeaders()).andReturn(new HttpHeaders());
+ expect(request.execute()).andThrow(new IOException());
+
+ replayMocks();
+
+ try {
+ template.getForObject("http://example.com/resource", String.class);
+ fail("RestClientException expected");
+ }
+ catch (HttpIOException ex) {
+ // expected
+ }
+
+ verifyMocks();
+ }
+
+ private void replayMocks() {
+ replay(requestFactory, request, response, errorHandler, converter);
+ }
+
+ private void verifyMocks() {
+ verify(requestFactory, request, response, errorHandler, converter);
+ }
+
+
+}