/*
* Copyright 2011 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.async.web.client;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.async.Promise;
import org.springframework.async.http.client.ClientHttpRequest;
import org.springframework.async.http.client.ClientHttpRequestFactory;
import org.springframework.async.http.client.ClientHttpResponse;
import org.springframework.async.http.client.support.HttpAccessor;
import org.springframework.async.http.converter.HttpMessageConverter;
import org.springframework.async.http.converter.StringHttpMessageConverter;
import org.springframework.async.web.client.grizzly.GrizzlyClientHttpRequestFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.client.RestClientException;
import org.springframework.web.util.UriTemplate;
import org.springframework.web.util.UriUtils;
/**
* @author Jon Brisbin <jon@jbrisbin.com>
*/
public class AsyncRestTemplate extends HttpAccessor implements AsyncRestOperations {
private static final boolean jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", AsyncRestTemplate.class.getClassLoader());
private static final boolean jacksonPresent = ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AsyncRestTemplate.class.getClassLoader())
&& ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AsyncRestTemplate.class.getClassLoader());
private static final boolean romePresent = ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", AsyncRestTemplate.class.getClassLoader());
private static final boolean grizzlyPresent = ClassUtils.isPresent("org.glassfish.grizzly.Grizzly", AsyncRestTemplate.class.getClassLoader());
private final ResponseExtractor<HttpHeaders> headersExtractor = new HeadersExtractor();
private final Logger log = LoggerFactory.getLogger(getClass());
private List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler();
public AsyncRestTemplate() {
// this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
// this.messageConverters.add(new ResourceHttpMessageConverter());
// this.messageConverters.add(new SourceHttpMessageConverter());
// this.messageConverters.add(new XmlAwareFormHttpMessageConverter());
if (jaxb2Present) {
// this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (jacksonPresent) {
// this.messageConverters.add(new MappingJacksonHttpMessageConverter());
}
if (romePresent) {
// this.messageConverters.add(new AtomFeedHttpMessageConverter());
// this.messageConverters.add(new RssChannelHttpMessageConverter());
}
if (grizzlyPresent) {
setRequestFactory(new GrizzlyClientHttpRequestFactory());
} else {
// TODO: simple version
}
}
public AsyncRestTemplate(ClientHttpRequestFactory requestFactory) {
this();
setRequestFactory(requestFactory);
}
/**
* Set the message body converters to use. These converters are used to convert from and to HTTP requests and
* responses.
*/
public AsyncRestTemplate setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
this.messageConverters = messageConverters;
return this;
}
/**
* Returns the message body converters. These converters are used to convert from and to HTTP requests and responses.
*/
public List<HttpMessageConverter<?>> getMessageConverters() {
return this.messageConverters;
}
/**
* Set the error handler.
*/
public AsyncRestTemplate setErrorHandler(ResponseErrorHandler errorHandler) {
Assert.notNull(errorHandler, "'errorHandler' must not be null");
this.errorHandler = errorHandler;
return this;
}
/**
* Return the error handler. By default, this is the {@link DefaultResponseErrorHandler}.
*/
public ResponseErrorHandler getErrorHandler() {
return this.errorHandler;
}
@Override
public <T> Promise<T> getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> extractor = new HttpMessageConverterExtractor<T>(responseType, messageConverters);
return execute(url, HttpMethod.GET, requestCallback, extractor, uriVariables);
}
@Override
public <T> Promise<T> getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
return null;
}
@Override public <T> Promise<T> getForObject(URI url, Class<T> responseType) throws RestClientException {
return null;
}
@Override
public <T> Promise<ResponseEntity<T>> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
return null;
}
@Override
public <T> Promise<ResponseEntity<T>> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
return null;
}
@Override
public <T> Promise<ResponseEntity<T>> getForEntity(URI url, Class<T> responseType) throws RestClientException {
return null;
}
@Override public Promise<HttpHeaders> headForHeaders(String url, Object... uriVariables) throws RestClientException {
return null;
}
@Override
public Promise<HttpHeaders> headForHeaders(String url, Map<String, ?> uriVariables) throws RestClientException {
return null;
}
@Override public Promise<HttpHeaders> headForHeaders(URI url) throws RestClientException {
return null;
}
@Override
public Promise<URI> postForLocation(String url, Object request, Object... uriVariables) throws RestClientException {
return null;
}
@Override
public Promise<URI> postForLocation(String url, Object request, Map<String, ?> uriVariables) throws RestClientException {
return null;
}
@Override public Promise<URI> postForLocation(URI url, Object request) throws RestClientException {
return null;
}
@Override
public <T> Promise<T> postForObject(String url, Object request, Class<T> responseType, Object... uriVariables) throws RestClientException {
return null;
}
@Override
public <T> Promise<T> postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
return null;
}
@Override
public <T> Promise<T> postForObject(URI url, Object request, Class<T> responseType) throws RestClientException {
return null;
}
@Override
public <T> Promise<ResponseEntity<T>> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables) throws RestClientException {
return null;
}
@Override
public <T> Promise<ResponseEntity<T>> postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
return null;
}
@Override
public <T> Promise<ResponseEntity<T>> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException {
return null;
}
@Override public Promise<Void> put(String url, Object request, Object... uriVariables) throws RestClientException {
// HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request);
execute(url, HttpMethod.PUT, null, null, uriVariables);
return null;
}
@Override
public Promise<Void> put(String url, Object request, Map<String, ?> uriVariables) throws RestClientException {
return null;
}
@Override public Promise<Void> put(URI url, Object request) throws RestClientException {
return null;
}
@Override public Promise<Void> delete(String url, Object... uriVariables) throws RestClientException {
return null;
}
@Override public Promise<Void> delete(String url, Map<String, ?> uriVariables) throws RestClientException {
return null;
}
@Override public Promise<Void> delete(URI url) throws RestClientException {
return null;
}
@Override
public Promise<Set<HttpMethod>> optionsForAllow(String url, Object... uriVariables) throws RestClientException {
return null;
}
@Override
public Promise<Set<HttpMethod>> optionsForAllow(String url, Map<String, ?> uriVariables) throws RestClientException {
return null;
}
@Override public Promise<Set<HttpMethod>> optionsForAllow(URI url) throws RestClientException {
return null;
}
@Override
public <T> Promise<ResponseEntity<T>> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity,
Class<T> responseType, Object... uriVariables)
throws RestClientException {
return null;
}
@Override
public <T> Promise<ResponseEntity<T>> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity,
Class<T> responseType, Map<String, ?> uriVariables)
throws RestClientException {
return null;
}
@Override
public <T> Promise<ResponseEntity<T>> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity,
Class<T> responseType) throws RestClientException {
return null;
}
@Override
public <T> Promise<T> execute(String url, HttpMethod method, RequestCallback requestCallback,
ResponseExtractor<T> responseExtractor, Object... uriVariables)
throws RestClientException {
UriTemplate uriTemplate = new HttpUrlTemplate(url);
URI expanded = uriTemplate.expand(uriVariables);
return doExecute(expanded, method, requestCallback, responseExtractor);
}
@Override
public <T> Promise<T> execute(String url, HttpMethod method, RequestCallback requestCallback,
ResponseExtractor<T> responseExtractor, Map<String, ?> uriVariables)
throws RestClientException {
return null;
}
@Override
public <T> Promise<T> execute(URI url, HttpMethod method, RequestCallback requestCallback,
ResponseExtractor<T> responseExtractor) throws RestClientException {
return null;
}
/**
* Execute the given method on the provided URI. The {@link ClientHttpRequest} is processed using the {@link
* RequestCallback}; the response with the {@link ResponseExtractor}.
*
* @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 ResponseExtractor}
*/
protected <T> Promise<T> doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
ResponseExtractor<T> responseExtractor) throws RestClientException {
ClientHttpResponse response = null;
try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
if (!getErrorHandler().hasError(response)) {
logResponseStatus(method, url, response);
} else {
handleResponseError(method, url, response);
}
if (responseExtractor != null) {
return responseExtractor.extractData(response);
} else {
return null;
}
} catch (IOException e) {
throw new RestClientException(e.getMessage(), e);
}
}
private void logResponseStatus(HttpMethod method, URI url, ClientHttpResponse response) {
if (log.isDebugEnabled()) {
try {
log.debug(
method.name() + " request for \"" + url + "\" resulted in " + response.getStatusCode() + " (" +
response.getStatusText() + ")");
} catch (IOException e) {
// ignore
}
}
}
private void handleResponseError(HttpMethod method, URI url, ClientHttpResponse response) throws IOException {
if (log.isWarnEnabled()) {
try {
log.warn(
method.name() + " request for \"" + url + "\" resulted in " + response.getStatusCode() + " (" +
response.getStatusText() + "); invoking error handler");
} catch (IOException e) {
// ignore
}
}
getErrorHandler().handleError(response);
}
/**
* Request callback implementation that prepares the request's accept headers.
*/
private class AcceptHeaderRequestCallback implements RequestCallback {
private final Class<?> responseType;
private AcceptHeaderRequestCallback(Class<?> responseType) {
this.responseType = responseType;
}
@SuppressWarnings("unchecked")
public void doWithRequest(ClientHttpRequest request) throws IOException {
if (responseType != null) {
final List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
if (messageConverter.canRead(responseType, null)) {
List<MediaType> supportedMediaTypes = messageConverter.getSupportedMediaTypes();
for (MediaType supportedMediaType : supportedMediaTypes) {
if (supportedMediaType.getCharSet() != null) {
supportedMediaType =
new MediaType(supportedMediaType.getType(), supportedMediaType.getSubtype());
}
allSupportedMediaTypes.add(supportedMediaType);
}
}
}
if (!allSupportedMediaTypes.isEmpty()) {
MediaType.sortBySpecificity(allSupportedMediaTypes);
if (log.isDebugEnabled()) {
log.debug("Setting request Accept header to " + allSupportedMediaTypes);
}
request.getHeaders().setAccept(allSupportedMediaTypes);
}
}
}
}
/**
* Request callback implementation that writes the given object to the request stream.
*/
private class HttpEntityRequestCallback extends AcceptHeaderRequestCallback {
private final HttpEntity requestEntity;
private HttpEntityRequestCallback(Object requestBody) {
this(requestBody, null);
}
@SuppressWarnings("unchecked")
private HttpEntityRequestCallback(Object requestBody, Class<?> responseType) {
super(responseType);
if (requestBody instanceof HttpEntity) {
this.requestEntity = (HttpEntity) requestBody;
} else if (requestBody != null) {
this.requestEntity = new HttpEntity(requestBody);
} else {
this.requestEntity = HttpEntity.EMPTY;
}
}
@Override
@SuppressWarnings("unchecked")
public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
super.doWithRequest(httpRequest);
if (!requestEntity.hasBody()) {
HttpHeaders headers = httpRequest.getHeaders();
HttpHeaders requestHeaders = requestEntity.getHeaders();
if (!requestHeaders.isEmpty()) {
headers.putAll(requestHeaders);
}
if (headers.getContentLength() == -1) {
headers.setContentLength(0L);
}
} else {
Object requestBody = requestEntity.getBody();
Class<?> requestType = requestBody.getClass();
HttpHeaders requestHeaders = requestEntity.getHeaders();
MediaType requestContentType = requestHeaders.getContentType();
for (HttpMessageConverter messageConverter : getMessageConverters()) {
if (messageConverter.canWrite(requestType, requestContentType)) {
if (!requestHeaders.isEmpty()) {
httpRequest.getHeaders().putAll(requestHeaders);
}
if (log.isDebugEnabled()) {
if (requestContentType != null) {
log.debug("Writing [" + requestBody + "] as \"" + requestContentType +
"\" using [" + messageConverter + "]");
} else {
log.debug("Writing [" + requestBody + "] using [" + messageConverter + "]");
}
}
messageConverter.write(requestBody, requestContentType, httpRequest);
return;
}
}
String message = "Could not write request: no suitable HttpMessageConverter found for request type [" +
requestType.getName() + "]";
if (requestContentType != null) {
message += " and content type [" + requestContentType + "]";
}
throw new RestClientException(message);
}
}
}
/**
* Response extractor that extracts the response {@link HttpHeaders}.
*/
private static class HeadersExtractor implements ResponseExtractor<HttpHeaders> {
public Promise<HttpHeaders> extractData(ClientHttpResponse response) throws IOException {
return response.getHeaders();
}
}
/**
* HTTP-specific subclass of UriTemplate, overriding the encode method.
*/
private static class HttpUrlTemplate extends UriTemplate {
public HttpUrlTemplate(String uriTemplate) {
super(uriTemplate);
}
@Override
protected URI encodeUri(String uri) {
try {
String encoded = UriUtils.encodeHttpUrl(uri, "UTF-8");
return new URI(encoded);
} catch (UnsupportedEncodingException ex) {
// should not happen, UTF-8 is always supported
throw new IllegalStateException(ex);
} catch (URISyntaxException ex) {
throw new IllegalArgumentException("Could not create HTTP URL from [" + uri + "]: " + ex, ex);
}
}
}
}