1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */

15
16 package software.amazon.awssdk.core.internal.handler;
17
18 import java.util.Optional;
19 import software.amazon.awssdk.annotations.SdkInternalApi;
20 import software.amazon.awssdk.core.Response;
21 import software.amazon.awssdk.core.SdkRequest;
22 import software.amazon.awssdk.core.SdkResponse;
23 import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
24 import software.amazon.awssdk.core.client.handler.ClientExecutionParams;
25 import software.amazon.awssdk.core.client.handler.SyncClientHandler;
26 import software.amazon.awssdk.core.exception.AbortedException;
27 import software.amazon.awssdk.core.exception.NonRetryableException;
28 import software.amazon.awssdk.core.exception.RetryableException;
29 import software.amazon.awssdk.core.http.ExecutionContext;
30 import software.amazon.awssdk.core.http.HttpResponseHandler;
31 import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
32 import software.amazon.awssdk.core.interceptor.InterceptorContext;
33 import software.amazon.awssdk.core.internal.http.AmazonSyncHttpClient;
34 import software.amazon.awssdk.core.internal.http.CombinedResponseHandler;
35 import software.amazon.awssdk.core.internal.http.InterruptMonitor;
36 import software.amazon.awssdk.core.sync.RequestBody;
37 import software.amazon.awssdk.core.sync.ResponseTransformer;
38 import software.amazon.awssdk.http.AbortableInputStream;
39 import software.amazon.awssdk.http.SdkHttpFullRequest;
40 import software.amazon.awssdk.http.SdkHttpFullResponse;
41
42 @SdkInternalApi
43 public abstract class BaseSyncClientHandler extends BaseClientHandler implements SyncClientHandler {
44     private final SdkClientConfiguration clientConfiguration;
45     private final AmazonSyncHttpClient client;
46
47     protected BaseSyncClientHandler(SdkClientConfiguration clientConfiguration,
48                                     AmazonSyncHttpClient client) {
49         super(clientConfiguration);
50         this.clientConfiguration = clientConfiguration;
51         this.client = client;
52     }
53
54     @Override
55     public <InputT extends SdkRequest, OutputT extends SdkResponse, ReturnT> ReturnT execute(
56         ClientExecutionParams<InputT, OutputT> executionParams,
57         ResponseTransformer<OutputT, ReturnT> responseTransformer) {
58
59         validateExecutionParams(executionParams);
60
61         if (executionParams.getCombinedResponseHandler() != null) {
62             // There is no support for catching errors in a body for streaming responses
63             throw new IllegalArgumentException("A streaming 'responseTransformer' may not be used when a "
64                                                + "'combinedResponseHandler' has been specified in a "
65                                                + "ClientExecutionParams object.");
66         }
67
68         ExecutionContext executionContext = createExecutionContext(executionParams, createInitialExecutionAttributes());
69
70         HttpResponseHandler<OutputT> decoratedResponseHandlers =
71             decorateResponseHandlers(executionParams.getResponseHandler(), executionContext);
72
73         HttpResponseHandler<ReturnT> httpResponseHandler =
74             new HttpResponseHandlerAdapter<>(decoratedResponseHandlers, responseTransformer);
75
76         return doExecute(
77             executionParams,
78             executionContext,
79             new CombinedResponseHandler<>(httpResponseHandler, executionParams.getErrorResponseHandler()));
80     }
81
82     @Override
83     public <InputT extends SdkRequest, OutputT extends SdkResponse> OutputT execute(
84         ClientExecutionParams<InputT, OutputT> executionParams) {
85
86         validateExecutionParams(executionParams);
87         ExecutionContext executionContext = createExecutionContext(executionParams, createInitialExecutionAttributes());
88         HttpResponseHandler<Response<OutputT>> combinedResponseHandler;
89
90         if (executionParams.getCombinedResponseHandler() != null) {
91             combinedResponseHandler = decorateSuccessResponseHandlers(executionParams.getCombinedResponseHandler(),
92                                                                       executionContext);
93         } else {
94             HttpResponseHandler<OutputT> decoratedResponseHandlers =
95                 decorateResponseHandlers(executionParams.getResponseHandler(), executionContext);
96
97             combinedResponseHandler = new CombinedResponseHandler<>(decoratedResponseHandlers,
98                                                                     executionParams.getErrorResponseHandler());
99         }
100
101         return doExecute(executionParams, executionContext, combinedResponseHandler);
102     }
103
104     @Override
105     public void close() {
106         client.close();
107     }
108
109     /**
110      * Invoke the request using the http client. Assumes credentials (or lack thereof) have been
111      * configured in the OldExecutionContext beforehand.
112      **/

113     private <OutputT> OutputT invoke(SdkHttpFullRequest request,
114                                        SdkRequest originalRequest,
115                                        ExecutionContext executionContext,
116                                        HttpResponseHandler<Response<OutputT>> responseHandler) {
117         return client.requestExecutionBuilder()
118                      .request(request)
119                      .originalRequest(originalRequest)
120                      .executionContext(executionContext)
121                      .execute(responseHandler);
122     }
123
124     private <InputT extends SdkRequest, OutputT, ReturnT> ReturnT doExecute(
125         ClientExecutionParams<InputT, OutputT> executionParams,
126         ExecutionContext executionContext,
127         HttpResponseHandler<Response<ReturnT>> responseHandler) {
128
129         InputT inputT = (InputT) finalizeSdkRequest(executionContext).request();
130
131         InterceptorContext sdkHttpFullRequestContext = finalizeSdkHttpFullRequest(executionParams,
132                                                                                   executionContext,
133                                                                                   inputT,
134                                                                                   clientConfiguration);
135
136         SdkHttpFullRequest marshalled = (SdkHttpFullRequest) sdkHttpFullRequestContext.httpRequest();
137
138         // TODO Pass requestBody as separate arg to invoke
139         Optional<RequestBody> requestBody = sdkHttpFullRequestContext.requestBody();
140
141         if (requestBody.isPresent()) {
142             marshalled = marshalled.toBuilder()
143                                    .contentStreamProvider(requestBody.get().contentStreamProvider())
144                                    .build();
145         }
146
147         return invoke(marshalled,
148                       inputT,
149                       executionContext,
150                       responseHandler);
151     }
152
153     private static class HttpResponseHandlerAdapter<ReturnT, OutputT extends SdkResponse>
154         implements HttpResponseHandler<ReturnT> {
155
156         private final HttpResponseHandler<OutputT> httpResponseHandler;
157         private final ResponseTransformer<OutputT, ReturnT> responseTransformer;
158
159         private HttpResponseHandlerAdapter(HttpResponseHandler<OutputT> httpResponseHandler,
160                                            ResponseTransformer<OutputT, ReturnT> responseTransformer) {
161             this.httpResponseHandler = httpResponseHandler;
162             this.responseTransformer = responseTransformer;
163         }
164
165         @Override
166         public ReturnT handle(SdkHttpFullResponse response, ExecutionAttributes executionAttributes) throws Exception {
167             OutputT resp = httpResponseHandler.handle(response, executionAttributes);
168             return transformResponse(resp, response.content().orElse(null));
169         }
170
171         @Override
172         public boolean needsConnectionLeftOpen() {
173             return responseTransformer.needsConnectionLeftOpen();
174         }
175
176
177         private ReturnT transformResponse(OutputT resp, AbortableInputStream inputStream) throws Exception {
178             try {
179                 InterruptMonitor.checkInterrupted();
180                 ReturnT result = responseTransformer.transform(resp, inputStream);
181                 InterruptMonitor.checkInterrupted();
182                 return result;
183             }  catch (RetryableException | InterruptedException | AbortedException e) {
184                 throw e;
185             } catch (Exception e) {
186                 InterruptMonitor.checkInterrupted();
187                 throw NonRetryableException.builder().cause(e).build();
188             }
189         }
190     }
191 }
192