1 /*
2  * Copyright 2010-2020 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 package com.amazonaws.http;
16
17 import com.amazonaws.AmazonWebServiceResponse;
18 import com.amazonaws.ResponseMetadata;
19 import com.amazonaws.internal.SdkFilterInputStream;
20 import com.amazonaws.transform.StaxUnmarshallerContext;
21 import com.amazonaws.transform.Unmarshaller;
22 import com.amazonaws.transform.VoidStaxUnmarshaller;
23 import com.amazonaws.util.StringUtils;
24 import com.amazonaws.util.XmlUtils;
25 import java.io.ByteArrayInputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.util.Map;
29 import javax.xml.stream.XMLEventReader;
30 import javax.xml.stream.XMLStreamException;
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33 import org.apache.http.impl.io.EmptyInputStream;
34
35 /**
36  * Default implementation of HttpResponseHandler that handles a successful
37  * response from an AWS service and unmarshalls the result using a StAX
38  * unmarshaller.
39  *
40  * @param <T> Indicates the type being unmarshalled by this response handler.
41  */

42 public class StaxResponseHandler<T> implements HttpResponseHandler<AmazonWebServiceResponse<T>> {
43
44     /**
45      * The StAX unmarshaller to use when handling the response
46      */

47     private Unmarshaller<T, StaxUnmarshallerContext> responseUnmarshaller;
48
49     /**
50      * Shared logger for profiling information
51      */

52     private static final Log log = LogFactory.getLog("com.amazonaws.request");
53
54     /**
55      * Constructs a new response handler that will use the specified StAX
56      * unmarshaller to unmarshall the service response and uses the specified
57      * response element path to find the root of the business data in the
58      * service's response.
59      *
60      * @param responseUnmarshaller The StAX unmarshaller to use on the response.
61      */

62     public StaxResponseHandler(Unmarshaller<T, StaxUnmarshallerContext> responseUnmarshaller) {
63         this.responseUnmarshaller = responseUnmarshaller;
64
65         /*
66          * Even if the invoked operation just returns null, we still need an
67          * unmarshaller to run so we can pull out response metadata.
68          *
69          * We might want to pass this in through the client class so that we
70          * don't have to do this check here.
71          */

72         if (this.responseUnmarshaller == null) {
73             this.responseUnmarshaller = new VoidStaxUnmarshaller<T>();
74         }
75     }
76
77
78     /**
79      * @see HttpResponseHandler#handle(HttpResponse)
80      */

81     public AmazonWebServiceResponse<T> handle(HttpResponse response) throws Exception {
82         log.trace("Parsing service response XML");
83         InputStream content = response.getContent();
84
85         if (content == null) {
86             content = new ByteArrayInputStream("<eof/>".getBytes(StringUtils.UTF8));
87         } else if (content instanceof SdkFilterInputStream &&
88                    ((SdkFilterInputStream) content).getDelegateStream() instanceof EmptyInputStream) {
89             content = new ByteArrayInputStream("<eof/>".getBytes(StringUtils.UTF8));
90         }
91
92         XMLEventReader eventReader;
93         try {
94             eventReader = XmlUtils.getXmlInputFactory().createXMLEventReader(content);
95         } catch (XMLStreamException e) {
96             throw handleXmlStreamException(e);
97         }
98
99         try {
100             AmazonWebServiceResponse<T> awsResponse = new AmazonWebServiceResponse<T>();
101             StaxUnmarshallerContext unmarshallerContext = new StaxUnmarshallerContext(eventReader, response.getHeaders());
102             unmarshallerContext.registerMetadataExpression("ResponseMetadata/RequestId", 2, ResponseMetadata.AWS_REQUEST_ID);
103             unmarshallerContext.registerMetadataExpression("requestId", 2, ResponseMetadata.AWS_REQUEST_ID);
104             registerAdditionalMetadataExpressions(unmarshallerContext);
105
106             T result = responseUnmarshaller.unmarshall(unmarshallerContext);
107             awsResponse.setResult(result);
108
109             Map<String, String> metadata = unmarshallerContext.getMetadata();
110             Map<String, String> responseHeaders = response.getHeaders();
111             if (responseHeaders != null) {
112                 if (responseHeaders.get(X_AMZN_REQUEST_ID_HEADER) != null) {
113                     metadata.put(ResponseMetadata.AWS_REQUEST_ID,
114                                  responseHeaders.get(X_AMZN_REQUEST_ID_HEADER));
115                 }
116                 if (responseHeaders.get(X_AMZN_EXTENDED_REQUEST_ID_HEADER) != null) {
117                     metadata.put(ResponseMetadata.AWS_EXTENDED_REQUEST_ID,
118                                  responseHeaders.get(X_AMZN_EXTENDED_REQUEST_ID_HEADER));
119                 }
120             }
121             awsResponse.setResponseMetadata(getResponseMetadata(metadata));
122
123             log.trace("Done parsing service response");
124             return awsResponse;
125         } catch (XMLStreamException e) {
126             throw handleXmlStreamException(e);
127         } finally {
128             try {
129                 eventReader.close();
130             } catch (XMLStreamException e) {
131                 log.warn("Error closing xml parser", e);
132             }
133         }
134     }
135
136     /**
137      * If the exception was caused by an {@link IOException}, wrap it an another IOE so
138      * that it will be exposed to the RetryPolicy.
139      */

140     private Exception handleXmlStreamException(XMLStreamException e) throws Exception {
141         if (e.getNestedException() instanceof IOException) {
142             return new IOException(e);
143         }
144         return e;
145     }
146
147     /**
148      * Create the default {@link ResponseMetadata}. Subclasses may override this to create a
149      * subclass of {@link ResponseMetadata}. Currently only SimpleDB does this.
150      */

151     protected ResponseMetadata getResponseMetadata(Map<String, String> metadata) {
152         return new ResponseMetadata(metadata);
153     }
154
155     /**
156      * Hook for subclasses to override in order to collect additional metadata
157      * from service responses.
158      *
159      * @param unmarshallerContext The unmarshaller context used to configure a service's response
160      *                            data.
161      */

162     protected void registerAdditionalMetadataExpressions(StaxUnmarshallerContext unmarshallerContext) {
163     }
164
165     /**
166      * Since this response handler completely consumes all the data from the
167      * underlying HTTP connection during the handle method, we don't need to
168      * keep the HTTP connection open.
169      *
170      * @see com.amazonaws.http.HttpResponseHandler#needsConnectionLeftOpen()
171      */

172     public boolean needsConnectionLeftOpen() {
173         return false;
174     }
175
176 }
177