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