Skip to content

Commit 55b1119

Browse files
committed
Metadata support for AWS/Query and EC2
1 parent e9a76d0 commit 55b1119

File tree

7 files changed

+258
-81
lines changed

7 files changed

+258
-81
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2010-2018 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.awscore.http.response;
17+
18+
import static software.amazon.awssdk.awscore.internal.DefaultAwsResponseMetadata.AWS_REQUEST_ID;
19+
20+
import java.io.IOException;
21+
import java.util.Map;
22+
import java.util.function.Function;
23+
import software.amazon.awssdk.annotations.SdkProtectedApi;
24+
import software.amazon.awssdk.awscore.AwsResponse;
25+
import software.amazon.awssdk.awscore.AwsResponseMetadata;
26+
import software.amazon.awssdk.awscore.internal.DefaultAwsResponseMetadata;
27+
import software.amazon.awssdk.core.SdkStandardLogger;
28+
import software.amazon.awssdk.core.http.HttpResponseHandler;
29+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
30+
import software.amazon.awssdk.core.internal.protocol.query.unmarshall.QueryProtocolUnmarshaller;
31+
import software.amazon.awssdk.core.protocol.SdkPojo;
32+
import software.amazon.awssdk.http.SdkHttpFullResponse;
33+
import software.amazon.awssdk.http.SdkHttpResponse;
34+
import software.amazon.awssdk.utils.Logger;
35+
import software.amazon.awssdk.utils.Pair;
36+
37+
/**
38+
* Response handler for AWS/Query services and Amazon EC2 which is a dialect of the Query protocol.
39+
*
40+
* @param <T> Indicates the type being unmarshalled by this response handler.
41+
*/
42+
@SdkProtectedApi
43+
public final class AwsQueryResponseHandler<T extends AwsResponse> implements HttpResponseHandler<T> {
44+
45+
private static final Logger log = Logger.loggerFor(StaxResponseHandler.class);
46+
47+
private final QueryProtocolUnmarshaller<T> unmarshaller;
48+
private final Function<SdkHttpFullResponse, SdkPojo> pojoSupplier;
49+
50+
51+
public AwsQueryResponseHandler(QueryProtocolUnmarshaller<T> unmarshaller,
52+
Function<SdkHttpFullResponse, SdkPojo> pojoSupplier) {
53+
this.unmarshaller = unmarshaller;
54+
this.pojoSupplier = pojoSupplier;
55+
}
56+
57+
@Override
58+
public T handle(SdkHttpFullResponse response, ExecutionAttributes executionAttributes) throws Exception {
59+
try {
60+
return unmarshallResponse(response);
61+
} finally {
62+
response.content().ifPresent(i -> {
63+
try {
64+
i.close();
65+
} catch (IOException e) {
66+
log.warn(() -> "Error closing HTTP content.", e);
67+
}
68+
});
69+
}
70+
}
71+
72+
@SuppressWarnings("unchecked")
73+
private T unmarshallResponse(SdkHttpFullResponse response) throws Exception {
74+
SdkStandardLogger.REQUEST_LOGGER.trace(() -> "Parsing service response XML.");
75+
Pair<T, Map<String, String>> result = unmarshaller.unmarshall(pojoSupplier.apply(response), response);
76+
SdkStandardLogger.REQUEST_LOGGER.trace(() -> "Done parsing service response.");
77+
AwsResponseMetadata responseMetadata = generateResponseMetadata(response, result.right());
78+
return (T) result.left().toBuilder().responseMetadata(responseMetadata).build();
79+
}
80+
81+
/**
82+
* Create the default {@link AwsResponseMetadata}. Subclasses may override this to create a
83+
* subclass of {@link AwsResponseMetadata}.
84+
*/
85+
private AwsResponseMetadata generateResponseMetadata(SdkHttpResponse response, Map<String, String> metadata) {
86+
if (!metadata.containsKey(AWS_REQUEST_ID)) {
87+
metadata.put(AWS_REQUEST_ID,
88+
response.firstMatchingHeader(X_AMZN_REQUEST_ID_HEADER).orElse(null));
89+
}
90+
91+
response.headers().forEach((key, value) -> metadata.put(key, value.get(0)));
92+
return DefaultAwsResponseMetadata.create(metadata);
93+
}
94+
95+
@Override
96+
public boolean needsConnectionLeftOpen() {
97+
// Query doesn't support streaming so this is always false
98+
return false;
99+
}
100+
101+
}

core/aws-core/src/main/java/software/amazon/awssdk/awscore/http/response/NewStaxResponseHandler.java

Lines changed: 0 additions & 68 deletions
This file was deleted.

core/aws-core/src/main/java/software/amazon/awssdk/awscore/protocol/query/AwsEc2ProtocolFactory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import software.amazon.awssdk.annotations.SdkProtectedApi;
2020
import software.amazon.awssdk.awscore.AwsRequest;
2121
import software.amazon.awssdk.awscore.AwsResponse;
22-
import software.amazon.awssdk.awscore.http.response.NewStaxResponseHandler;
22+
import software.amazon.awssdk.awscore.http.response.AwsQueryResponseHandler;
2323
import software.amazon.awssdk.core.Request;
2424
import software.amazon.awssdk.core.http.HttpResponseHandler;
2525
import software.amazon.awssdk.core.internal.protocol.query.marshall.QueryProtocolMarshaller;
@@ -44,6 +44,6 @@ public <T extends AwsRequest> ProtocolMarshaller<Request<T>> createProtocolMarsh
4444
}
4545

4646
public <T extends AwsResponse> HttpResponseHandler<T> createResponseHandler(Supplier<SdkPojo> pojoSupplier) {
47-
return new NewStaxResponseHandler<>(new QueryProtocolUnmarshaller<>(false), r -> pojoSupplier.get());
47+
return new AwsQueryResponseHandler<>(new QueryProtocolUnmarshaller<>(false), r -> pojoSupplier.get());
4848
}
4949
}

core/aws-core/src/main/java/software/amazon/awssdk/awscore/protocol/query/AwsQueryProtocolFactory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import java.util.function.Supplier;
1919
import software.amazon.awssdk.annotations.SdkProtectedApi;
2020
import software.amazon.awssdk.awscore.AwsResponse;
21-
import software.amazon.awssdk.awscore.http.response.NewStaxResponseHandler;
21+
import software.amazon.awssdk.awscore.http.response.AwsQueryResponseHandler;
2222
import software.amazon.awssdk.core.Request;
2323
import software.amazon.awssdk.core.http.HttpResponseHandler;
2424
import software.amazon.awssdk.core.internal.protocol.query.marshall.QueryProtocolMarshaller;
@@ -41,6 +41,6 @@ public <T extends software.amazon.awssdk.awscore.AwsRequest> ProtocolMarshaller<
4141
}
4242

4343
public <T extends AwsResponse> HttpResponseHandler<T> createResponseHandler(Supplier<SdkPojo> pojoSupplier) {
44-
return new NewStaxResponseHandler<>(new QueryProtocolUnmarshaller<>(true), r -> pojoSupplier.get());
44+
return new AwsQueryResponseHandler<>(new QueryProtocolUnmarshaller<>(true), r -> pojoSupplier.get());
4545
}
4646
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/protocol/query/unmarshall/ListQueryUnmarshaller.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ private List<XmlElement> getMembers(List<XmlElement> content, ListTrait listTrai
4242
return listTrait.isFlattened() ?
4343
content :
4444
// There have been cases in EC2 where the member name is not modeled correctly so we just grab all
45-
// direct children instead and don't care about member name.
45+
// direct children instead and don't care about member name. See TT0124273367 for more information.
4646
content.get(0).children();
4747
}
4848
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/protocol/query/unmarshall/QueryProtocolUnmarshaller.java

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515

1616
package software.amazon.awssdk.core.internal.protocol.query.unmarshall;
1717

18+
import java.util.HashMap;
1819
import java.util.List;
19-
import javax.xml.stream.XMLStreamException;
20+
import java.util.Map;
2021
import software.amazon.awssdk.annotations.SdkProtectedApi;
2122
import software.amazon.awssdk.core.internal.protocol.StringToValueConverter;
2223
import software.amazon.awssdk.core.protocol.MarshallingType;
@@ -25,6 +26,7 @@
2526
import software.amazon.awssdk.http.AbortableInputStream;
2627
import software.amazon.awssdk.http.SdkHttpFullResponse;
2728
import software.amazon.awssdk.utils.CollectionUtils;
29+
import software.amazon.awssdk.utils.Pair;
2830
import software.amazon.awssdk.utils.StringInputStream;
2931
import software.amazon.awssdk.utils.builder.SdkBuilder;
3032

@@ -36,6 +38,8 @@
3638
@SdkProtectedApi
3739
public class QueryProtocolUnmarshaller<TypeT extends SdkPojo> {
3840

41+
private static final String AWS_REQUEST_ID = "AWS_REQUEST_ID";
42+
3943
private static final QueryUnmarshallerRegistry UNMARSHALLER_REGISTRY = QueryUnmarshallerRegistry
4044
.builder()
4145
.unmarshaller(MarshallingType.STRING, new SimpleTypeQueryUnmarshaller<>(StringToValueConverter.TO_STRING))
@@ -59,19 +63,33 @@ public QueryProtocolUnmarshaller(boolean hasResultWrapper) {
5963
this.hasResultWrapper = hasResultWrapper;
6064
}
6165

62-
public TypeT unmarshall(SdkPojo sdkPojo,
63-
SdkHttpFullResponse response) throws Exception {
66+
public Pair<TypeT, Map<String, String>> unmarshall(SdkPojo sdkPojo,
67+
SdkHttpFullResponse response) throws Exception {
6468
QueryUnmarshallerContext unmarshallerContext = QueryUnmarshallerContext.builder()
6569
.registry(UNMARSHALLER_REGISTRY)
6670
.protocolUnmarshaller(this)
6771
.build();
68-
XmlElement root = parseXmlDocument(response);
69-
return (TypeT) unmarshall(unmarshallerContext, sdkPojo, root);
72+
XmlElement document = XmlDomParser.parse(response.content().orElseGet(this::emptyStream));
73+
XmlElement resultRoot = hasResultWrapper ? document.getFirstChild() : document;
74+
return Pair.of((TypeT) unmarshall(unmarshallerContext, sdkPojo, resultRoot), parseMetadata(document));
7075
}
7176

72-
private XmlElement parseXmlDocument(SdkHttpFullResponse response) throws XMLStreamException {
73-
XmlElement document = XmlDomParser.parse(response.content().orElseGet(this::emptyStream));
74-
return hasResultWrapper ? document.getFirstChild() : document;
77+
private Map<String, String> parseMetadata(XmlElement document) {
78+
XmlElement responseMetadata = document.getElementByName("ResponseMetadata");
79+
Map<String, String> metadata = new HashMap<>();
80+
if (responseMetadata != null) {
81+
responseMetadata.children().forEach(c -> metadata.put(metadataKeyName(c), c.textContent()));
82+
}
83+
XmlElement requestId = document.getElementByName("requestId");
84+
if (requestId != null) {
85+
// TODO move this to aws-core so we can reuse the constant
86+
metadata.put(AWS_REQUEST_ID, requestId.textContent());
87+
}
88+
return metadata;
89+
}
90+
91+
private String metadataKeyName(XmlElement c) {
92+
return c.elementName().equals("RequestId") ? AWS_REQUEST_ID : c.elementName();
7593
}
7694

7795
private SdkPojo unmarshall(QueryUnmarshallerContext context, SdkPojo sdkPojo, XmlElement root) {

0 commit comments

Comments
 (0)