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.protocols.xml;
17
18 import static java.util.Collections.unmodifiableList;
19
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.Optional;
23 import java.util.function.Function;
24 import java.util.function.Supplier;
25 import software.amazon.awssdk.annotations.SdkProtectedApi;
26 import software.amazon.awssdk.awscore.AwsResponse;
27 import software.amazon.awssdk.awscore.exception.AwsServiceException;
28 import software.amazon.awssdk.core.Response;
29 import software.amazon.awssdk.core.SdkPojo;
30 import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
31 import software.amazon.awssdk.core.client.config.SdkClientOption;
32 import software.amazon.awssdk.core.http.HttpResponseHandler;
33 import software.amazon.awssdk.core.internal.http.CombinedResponseHandler;
34 import software.amazon.awssdk.http.SdkHttpFullRequest;
35 import software.amazon.awssdk.protocols.core.ExceptionMetadata;
36 import software.amazon.awssdk.protocols.core.OperationInfo;
37 import software.amazon.awssdk.protocols.core.OperationMetadataAttribute;
38 import software.amazon.awssdk.protocols.core.ProtocolMarshaller;
39 import software.amazon.awssdk.protocols.query.unmarshall.AwsXmlErrorProtocolUnmarshaller;
40 import software.amazon.awssdk.protocols.query.unmarshall.XmlElement;
41 import software.amazon.awssdk.protocols.xml.internal.marshall.XmlGenerator;
42 import software.amazon.awssdk.protocols.xml.internal.marshall.XmlProtocolMarshaller;
43 import software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlErrorTransformer;
44 import software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlResponseHandler;
45 import software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlResponseTransformer;
46 import software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlUnmarshallingContext;
47 import software.amazon.awssdk.protocols.xml.internal.unmarshall.XmlProtocolUnmarshaller;
48
49 /**
50  * Factory to generate the various protocol handlers and generators to be used for
51  * communicating with REST/XML services.
52  */

53 @SdkProtectedApi
54 public class AwsXmlProtocolFactory {
55
56     /**
57      * Attribute for configuring the XML namespace to include in the xmlns attribute of the root element.
58      */

59     public static final OperationMetadataAttribute<String> XML_NAMESPACE_ATTRIBUTE =
60         new OperationMetadataAttribute<>(String.class);
61
62     /**
63      * Some services like Route53 specifies the location for the request shape. This should be the root of the
64      * generated xml document.
65      *
66      * Other services Cloudfront, s3 don't specify location param for the request shape. For them, this value will be null.
67      */

68     public static final OperationMetadataAttribute<String> ROOT_MARSHALL_LOCATION_ATTRIBUTE =
69         new OperationMetadataAttribute<>(String.class);
70
71     private static final XmlProtocolUnmarshaller XML_PROTOCOL_UNMARSHALLER = XmlProtocolUnmarshaller.create();
72
73     private final List<ExceptionMetadata> modeledExceptions;
74     private final Supplier<SdkPojo> defaultServiceExceptionSupplier;
75     private final AwsXmlErrorProtocolUnmarshaller errorUnmarshaller;
76     private final SdkClientConfiguration clientConfiguration;
77
78     AwsXmlProtocolFactory(Builder<?> builder) {
79         this.modeledExceptions = unmodifiableList(builder.modeledExceptions);
80         this.defaultServiceExceptionSupplier = builder.defaultServiceExceptionSupplier;
81         this.clientConfiguration = builder.clientConfiguration;
82         this.errorUnmarshaller = AwsXmlErrorProtocolUnmarshaller
83             .builder()
84             .defaultExceptionSupplier(defaultServiceExceptionSupplier)
85             .exceptions(modeledExceptions)
86             .errorUnmarshaller(XML_PROTOCOL_UNMARSHALLER)
87             .errorRootExtractor(this::getErrorRoot)
88             .build();
89     }
90
91     /**
92      * Creates an instance of {@link XmlProtocolMarshaller} to be used for marshalling the request.
93      *
94      * @param operationInfo Info required to marshall the request
95      */

96     public ProtocolMarshaller<SdkHttpFullRequest> createProtocolMarshaller(OperationInfo operationInfo) {
97         return XmlProtocolMarshaller.builder()
98                                     .endpoint(clientConfiguration.option(SdkClientOption.ENDPOINT))
99                                     .xmlGenerator(createGenerator(operationInfo))
100                                     .operationInfo(operationInfo)
101                                     .build();
102     }
103
104     public <T extends AwsResponse> HttpResponseHandler<T> createResponseHandler(Supplier<SdkPojo> pojoSupplier,
105                                                                                 XmlOperationMetadata staxOperationMetadata) {
106         return new AwsXmlResponseHandler<>(
107             XML_PROTOCOL_UNMARSHALLER, r -> pojoSupplier.get(),
108             staxOperationMetadata.isHasStreamingSuccessResponse());
109     }
110
111     protected <T extends AwsResponse> Function<AwsXmlUnmarshallingContext, T> createResponseTransformer(
112         Supplier<SdkPojo> pojoSupplier) {
113
114         return new AwsXmlResponseTransformer<>(
115             XML_PROTOCOL_UNMARSHALLER, r -> pojoSupplier.get());
116     }
117
118     protected Function<AwsXmlUnmarshallingContext, AwsServiceException> createErrorTransformer() {
119         return AwsXmlErrorTransformer.builder()
120                                      .defaultExceptionSupplier(defaultServiceExceptionSupplier)
121                                      .exceptions(modeledExceptions)
122                                      .errorUnmarshaller(XML_PROTOCOL_UNMARSHALLER)
123                                      .build();
124     }
125
126     public HttpResponseHandler<AwsServiceException> createErrorResponseHandler() {
127         return errorUnmarshaller;
128     }
129
130     public <T extends AwsResponse> HttpResponseHandler<Response<T>> createCombinedResponseHandler(
131         Supplier<SdkPojo> pojoSupplier, XmlOperationMetadata staxOperationMetadata) {
132
133         return new CombinedResponseHandler<>(createResponseHandler(pojoSupplier, staxOperationMetadata),
134                                              createErrorResponseHandler());
135     }
136
137     /**
138      * Extracts the <Error/> element from the root XML document. This method is protected as S3 has
139      * a slightly different location.
140      *
141      * @param document Root XML document.
142      * @return If error root is found than a fulfilled {@link Optional}, otherwise an empty one.
143      */

144     Optional<XmlElement> getErrorRoot(XmlElement document) {
145         return document.getOptionalElementByName("Error");
146     }
147
148     private XmlGenerator createGenerator(OperationInfo operationInfo) {
149         return operationInfo.hasPayloadMembers() ?
150                XmlGenerator.create(operationInfo.addtionalMetadata(XML_NAMESPACE_ATTRIBUTE)) :
151                null;
152     }
153
154     public static Builder builder() {
155         return new Builder();
156     }
157
158     /**
159      * Builder for {@link AwsXmlProtocolFactory}.
160      */

161     public static class Builder<SubclassT extends Builder> {
162
163         private final List<ExceptionMetadata> modeledExceptions = new ArrayList<>();
164         private Supplier<SdkPojo> defaultServiceExceptionSupplier;
165         private SdkClientConfiguration clientConfiguration;
166
167         Builder() {
168         }
169
170         /**
171          * Registers a new modeled exception by the error code.
172          *
173          * @param errorMetadata metadata for unmarshalling the exceptions
174          * @return This builder for method chaining.
175          */

176         public final SubclassT registerModeledException(ExceptionMetadata errorMetadata) {
177             modeledExceptions.add(errorMetadata);
178             return getSubclass();
179         }
180
181         /**
182          * A supplier for the services base exception builder. This is used when we can't identify any modeled
183          * exception to unmarshall into.
184          *
185          * @param exceptionBuilderSupplier Suppplier of the base service exceptions Builder.
186          * @return This builder for method chaining.
187          */

188         public SubclassT defaultServiceExceptionSupplier(Supplier<SdkPojo> exceptionBuilderSupplier) {
189             this.defaultServiceExceptionSupplier = exceptionBuilderSupplier;
190             return getSubclass();
191         }
192
193         /**
194          * Sets the {@link SdkClientConfiguration} which contains the service endpoint.
195          *
196          * @param clientConfiguration Configuration of the client.
197          * @return This builder for method chaining.
198          */

199         public SubclassT clientConfiguration(SdkClientConfiguration clientConfiguration) {
200             this.clientConfiguration = clientConfiguration;
201             return getSubclass();
202         }
203
204         @SuppressWarnings("unchecked")
205         private SubclassT getSubclass() {
206             return (SubclassT) this;
207         }
208
209         public AwsXmlProtocolFactory build() {
210             return new AwsXmlProtocolFactory(this);
211         }
212     }
213 }
214