001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.client;
018
019import java.io.IOException;
020import java.io.OutputStream;
021import java.lang.reflect.Type;
022import java.net.URI;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.concurrent.ExecutionException;
027
028import org.springframework.core.ParameterizedTypeReference;
029import org.springframework.core.task.AsyncListenableTaskExecutor;
030import org.springframework.core.task.AsyncTaskExecutor;
031import org.springframework.core.task.SimpleAsyncTaskExecutor;
032import org.springframework.http.HttpEntity;
033import org.springframework.http.HttpHeaders;
034import org.springframework.http.HttpMethod;
035import org.springframework.http.ResponseEntity;
036import org.springframework.http.client.AsyncClientHttpRequest;
037import org.springframework.http.client.AsyncClientHttpRequestFactory;
038import org.springframework.http.client.ClientHttpRequest;
039import org.springframework.http.client.ClientHttpRequestFactory;
040import org.springframework.http.client.ClientHttpResponse;
041import org.springframework.http.client.SimpleClientHttpRequestFactory;
042import org.springframework.http.client.support.InterceptingAsyncHttpAccessor;
043import org.springframework.http.converter.HttpMessageConverter;
044import org.springframework.util.Assert;
045import org.springframework.util.concurrent.ListenableFuture;
046import org.springframework.util.concurrent.ListenableFutureAdapter;
047import org.springframework.web.util.AbstractUriTemplateHandler;
048import org.springframework.web.util.UriTemplateHandler;
049
050/**
051 * <strong>Spring's central class for asynchronous client-side HTTP access.</strong>
052 * Exposes similar methods as {@link RestTemplate}, but returns {@link ListenableFuture}
053 * wrappers as opposed to concrete results.
054 *
055 * <p>The {@code AsyncRestTemplate} exposes a synchronous {@link RestTemplate} via the
056 * {@link #getRestOperations()} method and shares its {@linkplain #setErrorHandler error handler}
057 * and {@linkplain #setMessageConverters message converters} with that {@code RestTemplate}.
058 *
059 * <p><strong>Note:</strong> by default {@code AsyncRestTemplate} relies on
060 * standard JDK facilities to establish HTTP connections. You can switch to use
061 * a different HTTP library such as Apache HttpComponents, Netty, and OkHttp by
062 * using a constructor accepting an {@link AsyncClientHttpRequestFactory}.
063 *
064 * <p>For more information, please refer to the {@link RestTemplate} API documentation.
065 *
066 * @author Arjen Poutsma
067 * @since 4.0
068 * @see RestTemplate
069 */
070public class AsyncRestTemplate extends InterceptingAsyncHttpAccessor implements AsyncRestOperations {
071
072        private final RestTemplate syncTemplate;
073
074
075        /**
076         * Create a new instance of the {@code AsyncRestTemplate} using default settings.
077         * <p>This constructor uses a {@link SimpleClientHttpRequestFactory} in combination
078         * with a {@link SimpleAsyncTaskExecutor} for asynchronous execution.
079         */
080        public AsyncRestTemplate() {
081                this(new SimpleAsyncTaskExecutor());
082        }
083
084        /**
085         * Create a new instance of the {@code AsyncRestTemplate} using the given
086         * {@link AsyncTaskExecutor}.
087         * <p>This constructor uses a {@link SimpleClientHttpRequestFactory} in combination
088         * with the given {@code AsyncTaskExecutor} for asynchronous execution.
089         */
090        public AsyncRestTemplate(AsyncListenableTaskExecutor taskExecutor) {
091                Assert.notNull(taskExecutor, "AsyncTaskExecutor must not be null");
092                SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
093                requestFactory.setTaskExecutor(taskExecutor);
094                this.syncTemplate = new RestTemplate(requestFactory);
095                setAsyncRequestFactory(requestFactory);
096        }
097
098        /**
099         * Create a new instance of the {@code AsyncRestTemplate} using the given
100         * {@link AsyncClientHttpRequestFactory}.
101         * <p>This constructor will cast the given asynchronous
102         * {@code AsyncClientHttpRequestFactory} to a {@link ClientHttpRequestFactory}. Since
103         * all implementations of {@code ClientHttpRequestFactory} provided in Spring also
104         * implement {@code AsyncClientHttpRequestFactory}, this should not result in a
105         * {@code ClassCastException}.
106         */
107        public AsyncRestTemplate(AsyncClientHttpRequestFactory asyncRequestFactory) {
108                this(asyncRequestFactory, (ClientHttpRequestFactory) asyncRequestFactory);
109        }
110
111        /**
112         * Creates a new instance of the {@code AsyncRestTemplate} using the given
113         * asynchronous and synchronous request factories.
114         * @param asyncRequestFactory the asynchronous request factory
115         * @param syncRequestFactory the synchronous request factory
116         */
117        public AsyncRestTemplate(
118                        AsyncClientHttpRequestFactory asyncRequestFactory, ClientHttpRequestFactory syncRequestFactory) {
119
120                this(asyncRequestFactory, new RestTemplate(syncRequestFactory));
121        }
122
123        /**
124         * Create a new instance of the {@code AsyncRestTemplate} using the given
125         * {@link AsyncClientHttpRequestFactory} and synchronous {@link RestTemplate}.
126         * @param requestFactory the asynchronous request factory to use
127         * @param restTemplate the synchronous template to use
128         */
129        public AsyncRestTemplate(AsyncClientHttpRequestFactory requestFactory, RestTemplate restTemplate) {
130                Assert.notNull(restTemplate, "RestTemplate must not be null");
131                this.syncTemplate = restTemplate;
132                setAsyncRequestFactory(requestFactory);
133        }
134
135
136        /**
137         * Set the error handler.
138         * <p>By default, AsyncRestTemplate uses a
139         * {@link org.springframework.web.client.DefaultResponseErrorHandler}.
140         */
141        public void setErrorHandler(ResponseErrorHandler errorHandler) {
142                this.syncTemplate.setErrorHandler(errorHandler);
143        }
144
145        /**
146         * Return the error handler.
147         */
148        public ResponseErrorHandler getErrorHandler() {
149                return this.syncTemplate.getErrorHandler();
150        }
151
152        /**
153         * Configure default URI variable values. This is a shortcut for:
154         * <pre class="code">
155         * DefaultUriTemplateHandler handler = new DefaultUriTemplateHandler();
156         * handler.setDefaultUriVariables(...);
157         *
158         * AsyncRestTemplate restTemplate = new AsyncRestTemplate();
159         * restTemplate.setUriTemplateHandler(handler);
160         * </pre>
161         * @param defaultUriVariables the default URI variable values
162         * @since 4.3
163         */
164        public void setDefaultUriVariables(Map<String, ?> defaultUriVariables) {
165                UriTemplateHandler handler = this.syncTemplate.getUriTemplateHandler();
166                Assert.isInstanceOf(AbstractUriTemplateHandler.class, handler,
167                                "Can only use this property in conjunction with a DefaultUriTemplateHandler");
168                ((AbstractUriTemplateHandler) handler).setDefaultUriVariables(defaultUriVariables);
169        }
170
171        /**
172         * This property has the same purpose as the corresponding property on the
173         * {@code RestTemplate}. For more details see
174         * {@link RestTemplate#setUriTemplateHandler}.
175         * @param handler the URI template handler to use
176         */
177        public void setUriTemplateHandler(UriTemplateHandler handler) {
178                this.syncTemplate.setUriTemplateHandler(handler);
179        }
180
181        /**
182         * Return the configured URI template handler.
183         */
184        public UriTemplateHandler getUriTemplateHandler() {
185                return this.syncTemplate.getUriTemplateHandler();
186        }
187
188        @Override
189        public RestOperations getRestOperations() {
190                return this.syncTemplate;
191        }
192
193        /**
194         * Set the message body converters to use.
195         * <p>These converters are used to convert from and to HTTP requests and responses.
196         */
197        public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
198                this.syncTemplate.setMessageConverters(messageConverters);
199        }
200
201        /**
202         * Return the message body converters.
203         */
204        public List<HttpMessageConverter<?>> getMessageConverters() {
205                return this.syncTemplate.getMessageConverters();
206        }
207
208
209        // GET
210
211        @Override
212        public <T> ListenableFuture<ResponseEntity<T>> getForEntity(String url, Class<T> responseType, Object... uriVariables)
213                        throws RestClientException {
214
215                AsyncRequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
216                ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
217                return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
218        }
219
220        @Override
221        public <T> ListenableFuture<ResponseEntity<T>> getForEntity(String url, Class<T> responseType,
222                        Map<String, ?> uriVariables) throws RestClientException {
223
224                AsyncRequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
225                ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
226                return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
227        }
228
229        @Override
230        public <T> ListenableFuture<ResponseEntity<T>> getForEntity(URI url, Class<T> responseType) throws RestClientException {
231                AsyncRequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
232                ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
233                return execute(url, HttpMethod.GET, requestCallback, responseExtractor);
234        }
235
236
237        // HEAD
238
239        @Override
240        public ListenableFuture<HttpHeaders> headForHeaders(String url, Object... uriVariables) throws RestClientException {
241                ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
242                return execute(url, HttpMethod.HEAD, null, headersExtractor, uriVariables);
243        }
244
245        @Override
246        public ListenableFuture<HttpHeaders> headForHeaders(String url, Map<String, ?> uriVariables) throws RestClientException {
247                ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
248                return execute(url, HttpMethod.HEAD, null, headersExtractor, uriVariables);
249        }
250
251        @Override
252        public ListenableFuture<HttpHeaders> headForHeaders(URI url) throws RestClientException {
253                ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
254                return execute(url, HttpMethod.HEAD, null, headersExtractor);
255        }
256
257
258        // POST
259
260        @Override
261        public ListenableFuture<URI> postForLocation(String url, HttpEntity<?> request, Object... uriVars)
262                        throws RestClientException {
263
264                AsyncRequestCallback callback = httpEntityCallback(request);
265                ResponseExtractor<HttpHeaders> extractor = headersExtractor();
266                ListenableFuture<HttpHeaders> future = execute(url, HttpMethod.POST, callback, extractor, uriVars);
267                return adaptToLocationHeader(future);
268        }
269
270        @Override
271        public ListenableFuture<URI> postForLocation(String url, HttpEntity<?> request, Map<String, ?> uriVars)
272                        throws RestClientException {
273
274                AsyncRequestCallback callback = httpEntityCallback(request);
275                ResponseExtractor<HttpHeaders> extractor = headersExtractor();
276                ListenableFuture<HttpHeaders> future = execute(url, HttpMethod.POST, callback, extractor, uriVars);
277                return adaptToLocationHeader(future);
278        }
279
280        @Override
281        public ListenableFuture<URI> postForLocation(URI url, HttpEntity<?> request) throws RestClientException {
282                AsyncRequestCallback callback = httpEntityCallback(request);
283                ResponseExtractor<HttpHeaders> extractor = headersExtractor();
284                ListenableFuture<HttpHeaders> future = execute(url, HttpMethod.POST, callback, extractor);
285                return adaptToLocationHeader(future);
286        }
287
288        private static ListenableFuture<URI> adaptToLocationHeader(ListenableFuture<HttpHeaders> future) {
289                return new ListenableFutureAdapter<URI, HttpHeaders>(future) {
290                        @Override
291                        protected URI adapt(HttpHeaders headers) throws ExecutionException {
292                                return headers.getLocation();
293                        }
294                };
295        }
296
297        @Override
298        public <T> ListenableFuture<ResponseEntity<T>> postForEntity(String url, HttpEntity<?> request,
299                        Class<T> responseType, Object... uriVariables) throws RestClientException {
300
301                AsyncRequestCallback requestCallback = httpEntityCallback(request, responseType);
302                ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
303                return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
304        }
305
306        @Override
307        public <T> ListenableFuture<ResponseEntity<T>> postForEntity(String url, HttpEntity<?> request,
308                        Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
309
310                AsyncRequestCallback requestCallback = httpEntityCallback(request, responseType);
311                ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
312                return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
313        }
314
315        @Override
316        public <T> ListenableFuture<ResponseEntity<T>> postForEntity(URI url, HttpEntity<?> request, Class<T> responseType)
317                        throws RestClientException {
318
319                AsyncRequestCallback requestCallback = httpEntityCallback(request, responseType);
320                ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
321                return execute(url, HttpMethod.POST, requestCallback, responseExtractor);
322        }
323
324
325        // PUT
326
327        @Override
328        public ListenableFuture<?> put(String url, HttpEntity<?> request, Object... uriVariables) throws RestClientException {
329                AsyncRequestCallback requestCallback = httpEntityCallback(request);
330                return execute(url, HttpMethod.PUT, requestCallback, null, uriVariables);
331        }
332
333        @Override
334        public ListenableFuture<?> put(String url, HttpEntity<?> request, Map<String, ?> uriVariables) throws RestClientException {
335                AsyncRequestCallback requestCallback = httpEntityCallback(request);
336                return execute(url, HttpMethod.PUT, requestCallback, null, uriVariables);
337        }
338
339        @Override
340        public ListenableFuture<?> put(URI url, HttpEntity<?> request) throws RestClientException {
341                AsyncRequestCallback requestCallback = httpEntityCallback(request);
342                return execute(url, HttpMethod.PUT, requestCallback, null);
343        }
344
345
346        // DELETE
347
348        @Override
349        public ListenableFuture<?> delete(String url, Object... uriVariables) throws RestClientException {
350                return execute(url, HttpMethod.DELETE, null, null, uriVariables);
351        }
352
353        @Override
354        public ListenableFuture<?> delete(String url, Map<String, ?> uriVariables) throws RestClientException {
355                return execute(url, HttpMethod.DELETE, null, null, uriVariables);
356        }
357
358        @Override
359        public ListenableFuture<?> delete(URI url) throws RestClientException {
360                return execute(url, HttpMethod.DELETE, null, null);
361        }
362
363
364        // OPTIONS
365
366        @Override
367        public ListenableFuture<Set<HttpMethod>> optionsForAllow(String url, Object... uriVars) throws RestClientException {
368                ResponseExtractor<HttpHeaders> extractor = headersExtractor();
369                ListenableFuture<HttpHeaders> future = execute(url, HttpMethod.OPTIONS, null, extractor, uriVars);
370                return adaptToAllowHeader(future);
371        }
372
373        @Override
374        public ListenableFuture<Set<HttpMethod>> optionsForAllow(String url, Map<String, ?> uriVars) throws RestClientException {
375                ResponseExtractor<HttpHeaders> extractor = headersExtractor();
376                ListenableFuture<HttpHeaders> future = execute(url, HttpMethod.OPTIONS, null, extractor, uriVars);
377                return adaptToAllowHeader(future);
378        }
379
380        @Override
381        public ListenableFuture<Set<HttpMethod>> optionsForAllow(URI url) throws RestClientException {
382                ResponseExtractor<HttpHeaders> extractor = headersExtractor();
383                ListenableFuture<HttpHeaders> future = execute(url, HttpMethod.OPTIONS, null, extractor);
384                return adaptToAllowHeader(future);
385        }
386
387        private static ListenableFuture<Set<HttpMethod>> adaptToAllowHeader(ListenableFuture<HttpHeaders> future) {
388                return new ListenableFutureAdapter<Set<HttpMethod>, HttpHeaders>(future) {
389                        @Override
390                        protected Set<HttpMethod> adapt(HttpHeaders headers) throws ExecutionException {
391                                return headers.getAllow();
392                        }
393                };
394        }
395
396        // exchange
397
398        @Override
399        public <T> ListenableFuture<ResponseEntity<T>> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity,
400                        Class<T> responseType, Object... uriVariables) throws RestClientException {
401
402                AsyncRequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
403                ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
404                return execute(url, method, requestCallback, responseExtractor, uriVariables);
405        }
406
407        @Override
408        public <T> ListenableFuture<ResponseEntity<T>> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity,
409                        Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
410
411                AsyncRequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
412                ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
413                return execute(url, method, requestCallback, responseExtractor, uriVariables);
414        }
415
416        @Override
417        public <T> ListenableFuture<ResponseEntity<T>> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity,
418                        Class<T> responseType) throws RestClientException {
419
420                AsyncRequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
421                ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
422                return execute(url, method, requestCallback, responseExtractor);
423        }
424
425        @Override
426        public <T> ListenableFuture<ResponseEntity<T>> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity,
427                        ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException {
428
429                Type type = responseType.getType();
430                AsyncRequestCallback requestCallback = httpEntityCallback(requestEntity, type);
431                ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type);
432                return execute(url, method, requestCallback, responseExtractor, uriVariables);
433        }
434
435        @Override
436        public <T> ListenableFuture<ResponseEntity<T>> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity,
437                        ParameterizedTypeReference<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
438
439                Type type = responseType.getType();
440                AsyncRequestCallback requestCallback = httpEntityCallback(requestEntity, type);
441                ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type);
442                return execute(url, method, requestCallback, responseExtractor, uriVariables);
443        }
444
445        @Override
446        public <T> ListenableFuture<ResponseEntity<T>> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity,
447                        ParameterizedTypeReference<T> responseType) throws RestClientException {
448
449                Type type = responseType.getType();
450                AsyncRequestCallback requestCallback = httpEntityCallback(requestEntity, type);
451                ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type);
452                return execute(url, method, requestCallback, responseExtractor);
453        }
454
455
456        // general execution
457
458        @Override
459        public <T> ListenableFuture<T> execute(String url, HttpMethod method, AsyncRequestCallback requestCallback,
460                        ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {
461
462                URI expanded = getUriTemplateHandler().expand(url, uriVariables);
463                return doExecute(expanded, method, requestCallback, responseExtractor);
464        }
465
466        @Override
467        public <T> ListenableFuture<T> execute(String url, HttpMethod method, AsyncRequestCallback requestCallback,
468                        ResponseExtractor<T> responseExtractor, Map<String, ?> uriVariables) throws RestClientException {
469
470                URI expanded = getUriTemplateHandler().expand(url, uriVariables);
471                return doExecute(expanded, method, requestCallback, responseExtractor);
472        }
473
474        @Override
475        public <T> ListenableFuture<T> execute(URI url, HttpMethod method, AsyncRequestCallback requestCallback,
476                        ResponseExtractor<T> responseExtractor) throws RestClientException {
477
478                return doExecute(url, method, requestCallback, responseExtractor);
479        }
480
481        /**
482         * Execute the given method on the provided URI. The
483         * {@link org.springframework.http.client.ClientHttpRequest}
484         * is processed using the {@link RequestCallback}; the response with
485         * the {@link ResponseExtractor}.
486         * @param url the fully-expanded URL to connect to
487         * @param method the HTTP method to execute (GET, POST, etc.)
488         * @param requestCallback object that prepares the request (can be {@code null})
489         * @param responseExtractor object that extracts the return value from the response (can
490         * be {@code null})
491         * @return an arbitrary object, as returned by the {@link ResponseExtractor}
492         */
493        protected <T> ListenableFuture<T> doExecute(URI url, HttpMethod method, AsyncRequestCallback requestCallback,
494                        ResponseExtractor<T> responseExtractor) throws RestClientException {
495
496                Assert.notNull(url, "'url' must not be null");
497                Assert.notNull(method, "'method' must not be null");
498                try {
499                        AsyncClientHttpRequest request = createAsyncRequest(url, method);
500                        if (requestCallback != null) {
501                                requestCallback.doWithRequest(request);
502                        }
503                        ListenableFuture<ClientHttpResponse> responseFuture = request.executeAsync();
504                        return new ResponseExtractorFuture<T>(method, url, responseFuture, responseExtractor);
505                }
506                catch (IOException ex) {
507                        throw new ResourceAccessException("I/O error on " + method.name() +
508                                        " request for \"" + url + "\":" + ex.getMessage(), ex);
509                }
510        }
511
512        private void logResponseStatus(HttpMethod method, URI url, ClientHttpResponse response) {
513                if (logger.isDebugEnabled()) {
514                        try {
515                                logger.debug("Async " + method.name() + " request for \"" + url + "\" resulted in " +
516                                                response.getRawStatusCode() + " (" + response.getStatusText() + ")");
517                        }
518                        catch (IOException ex) {
519                                // ignore
520                        }
521                }
522        }
523
524        private void handleResponseError(HttpMethod method, URI url, ClientHttpResponse response) throws IOException {
525                if (logger.isWarnEnabled()) {
526                        try {
527                                logger.warn("Async " + method.name() + " request for \"" + url + "\" resulted in " +
528                                                response.getRawStatusCode() + " (" + response.getStatusText() + "); invoking error handler");
529                        }
530                        catch (IOException ex) {
531                                // ignore
532                        }
533                }
534                getErrorHandler().handleError(response);
535        }
536
537        /**
538         * Returns a request callback implementation that prepares the request {@code Accept}
539         * headers based on the given response type and configured {@linkplain
540         * #getMessageConverters() message converters}.
541         */
542        protected <T> AsyncRequestCallback acceptHeaderRequestCallback(Class<T> responseType) {
543                return new AsyncRequestCallbackAdapter(this.syncTemplate.acceptHeaderRequestCallback(responseType));
544        }
545
546        /**
547         * Returns a request callback implementation that writes the given object to the
548         * request stream.
549         */
550        protected <T> AsyncRequestCallback httpEntityCallback(HttpEntity<T> requestBody) {
551                return new AsyncRequestCallbackAdapter(this.syncTemplate.httpEntityCallback(requestBody));
552        }
553
554        /**
555         * Returns a request callback implementation that writes the given object to the
556         * request stream.
557         */
558        protected <T> AsyncRequestCallback httpEntityCallback(HttpEntity<T> request, Type responseType) {
559                return new AsyncRequestCallbackAdapter(this.syncTemplate.httpEntityCallback(request, responseType));
560        }
561
562        /**
563         * Returns a response extractor for {@link ResponseEntity}.
564         */
565        protected <T> ResponseExtractor<ResponseEntity<T>> responseEntityExtractor(Type responseType) {
566                return this.syncTemplate.responseEntityExtractor(responseType);
567        }
568
569        /**
570         * Returns a response extractor for {@link HttpHeaders}.
571         */
572        protected ResponseExtractor<HttpHeaders> headersExtractor() {
573                return this.syncTemplate.headersExtractor();
574        }
575
576
577        /**
578         * Future returned from
579         * {@link #doExecute(URI, HttpMethod, AsyncRequestCallback, ResponseExtractor)}
580         */
581        private class ResponseExtractorFuture<T> extends ListenableFutureAdapter<T, ClientHttpResponse> {
582
583                private final HttpMethod method;
584
585                private final URI url;
586
587                private final ResponseExtractor<T> responseExtractor;
588
589                public ResponseExtractorFuture(HttpMethod method, URI url,
590                                ListenableFuture<ClientHttpResponse> clientHttpResponseFuture, ResponseExtractor<T> responseExtractor) {
591                        super(clientHttpResponseFuture);
592                        this.method = method;
593                        this.url = url;
594                        this.responseExtractor = responseExtractor;
595                }
596
597                @Override
598                protected final T adapt(ClientHttpResponse response) throws ExecutionException {
599                        try {
600                                if (!getErrorHandler().hasError(response)) {
601                                        logResponseStatus(this.method, this.url, response);
602                                }
603                                else {
604                                        handleResponseError(this.method, this.url, response);
605                                }
606                                return convertResponse(response);
607                        }
608                        catch (Throwable ex) {
609                                throw new ExecutionException(ex);
610                        }
611                        finally {
612                                if (response != null) {
613                                        response.close();
614                                }
615                        }
616                }
617
618                protected T convertResponse(ClientHttpResponse response) throws IOException {
619                        return (this.responseExtractor != null ? this.responseExtractor.extractData(response) : null);
620                }
621        }
622
623
624        /**
625         * Adapts a {@link RequestCallback} to the {@link AsyncRequestCallback} interface.
626         */
627        private static class AsyncRequestCallbackAdapter implements AsyncRequestCallback {
628
629                private final RequestCallback adaptee;
630
631                /**
632                 * Create a new {@code AsyncRequestCallbackAdapter} from the given
633                 * {@link RequestCallback}.
634                 * @param requestCallback the callback to base this adapter on
635                 */
636                public AsyncRequestCallbackAdapter(RequestCallback requestCallback) {
637                        this.adaptee = requestCallback;
638                }
639
640                @Override
641                public void doWithRequest(final AsyncClientHttpRequest request) throws IOException {
642                        if (this.adaptee != null) {
643                                this.adaptee.doWithRequest(new ClientHttpRequest() {
644                                        @Override
645                                        public ClientHttpResponse execute() throws IOException {
646                                                throw new UnsupportedOperationException("execute not supported");
647                                        }
648                                        @Override
649                                        public OutputStream getBody() throws IOException {
650                                                return request.getBody();
651                                        }
652                                        @Override
653                                        public HttpMethod getMethod() {
654                                                return request.getMethod();
655                                        }
656                                        @Override
657                                        public URI getURI() {
658                                                return request.getURI();
659                                        }
660                                        @Override
661                                        public HttpHeaders getHeaders() {
662                                                return request.getHeaders();
663                                        }
664                                });
665                        }
666                }
667        }
668
669}