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.http.MetricCollectingHttpResponseHandler; 34 import software.amazon.awssdk.core.internal.http.CombinedResponseHandler; 35 import software.amazon.awssdk.core.metrics.CoreMetric; 36 import software.amazon.awssdk.http.SdkHttpFullRequest; 37 import software.amazon.awssdk.http.SdkHttpFullResponse; 38 import software.amazon.awssdk.protocols.core.ExceptionMetadata; 39 import software.amazon.awssdk.protocols.core.OperationInfo; 40 import software.amazon.awssdk.protocols.core.OperationMetadataAttribute; 41 import software.amazon.awssdk.protocols.core.ProtocolMarshaller; 42 import software.amazon.awssdk.protocols.query.unmarshall.AwsXmlErrorProtocolUnmarshaller; 43 import software.amazon.awssdk.protocols.query.unmarshall.XmlElement; 44 import software.amazon.awssdk.protocols.xml.internal.marshall.XmlGenerator; 45 import software.amazon.awssdk.protocols.xml.internal.marshall.XmlProtocolMarshaller; 46 import software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlErrorTransformer; 47 import software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlResponseHandler; 48 import software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlResponseTransformer; 49 import software.amazon.awssdk.protocols.xml.internal.unmarshall.AwsXmlUnmarshallingContext; 50 import software.amazon.awssdk.protocols.xml.internal.unmarshall.XmlProtocolUnmarshaller; 51 import software.amazon.awssdk.protocols.xml.internal.unmarshall.XmlResponseHandler; 52 53 /** 54 * Factory to generate the various protocol handlers and generators to be used for 55 * communicating with REST/XML services. 56 */ 57 @SdkProtectedApi 58 public class AwsXmlProtocolFactory { 59 60 /** 61 * Attribute for configuring the XML namespace to include in the xmlns attribute of the root element. 62 */ 63 public static final OperationMetadataAttribute<String> XML_NAMESPACE_ATTRIBUTE = 64 new OperationMetadataAttribute<>(String.class); 65 66 /** 67 * Some services like Route53 specifies the location for the request shape. This should be the root of the 68 * generated xml document. 69 * 70 * Other services Cloudfront, s3 don't specify location param for the request shape. For them, this value will be null. 71 */ 72 public static final OperationMetadataAttribute<String> ROOT_MARSHALL_LOCATION_ATTRIBUTE = 73 new OperationMetadataAttribute<>(String.class); 74 75 private static final XmlProtocolUnmarshaller XML_PROTOCOL_UNMARSHALLER = XmlProtocolUnmarshaller.create(); 76 77 private final List<ExceptionMetadata> modeledExceptions; 78 private final Supplier<SdkPojo> defaultServiceExceptionSupplier; 79 private final HttpResponseHandler<AwsServiceException> errorUnmarshaller; 80 private final SdkClientConfiguration clientConfiguration; 81 AwsXmlProtocolFactory(Builder<?> builder)82 AwsXmlProtocolFactory(Builder<?> builder) { 83 this.modeledExceptions = unmodifiableList(builder.modeledExceptions); 84 this.defaultServiceExceptionSupplier = builder.defaultServiceExceptionSupplier; 85 this.clientConfiguration = builder.clientConfiguration; 86 87 this.errorUnmarshaller = timeUnmarshalling( 88 AwsXmlErrorProtocolUnmarshaller.builder() 89 .defaultExceptionSupplier(defaultServiceExceptionSupplier) 90 .exceptions(modeledExceptions) 91 .errorUnmarshaller(XML_PROTOCOL_UNMARSHALLER) 92 .errorRootExtractor(this::getErrorRoot) 93 .build()); 94 } 95 96 /** 97 * Creates an instance of {@link XmlProtocolMarshaller} to be used for marshalling the request. 98 * 99 * @param operationInfo Info required to marshall the request 100 */ createProtocolMarshaller(OperationInfo operationInfo)101 public ProtocolMarshaller<SdkHttpFullRequest> createProtocolMarshaller(OperationInfo operationInfo) { 102 return XmlProtocolMarshaller.builder() 103 .endpoint(clientConfiguration.option(SdkClientOption.ENDPOINT)) 104 .xmlGenerator(createGenerator(operationInfo)) 105 .operationInfo(operationInfo) 106 .build(); 107 } 108 createResponseHandler(Supplier<SdkPojo> pojoSupplier, XmlOperationMetadata staxOperationMetadata)109 public <T extends SdkPojo> HttpResponseHandler<T> createResponseHandler(Supplier<SdkPojo> pojoSupplier, 110 XmlOperationMetadata staxOperationMetadata) { 111 return createResponseHandler(r -> pojoSupplier.get(), staxOperationMetadata); 112 } 113 createResponseHandler(Function<SdkHttpFullResponse, SdkPojo> pojoSupplier, XmlOperationMetadata staxOperationMetadata)114 public <T extends SdkPojo> HttpResponseHandler<T> createResponseHandler(Function<SdkHttpFullResponse, SdkPojo> pojoSupplier, 115 XmlOperationMetadata staxOperationMetadata) { 116 return timeUnmarshalling( 117 new AwsXmlResponseHandler<>( 118 new XmlResponseHandler<>( 119 XML_PROTOCOL_UNMARSHALLER, pojoSupplier, 120 staxOperationMetadata.isHasStreamingSuccessResponse()))); 121 } 122 createResponseTransformer( Supplier<SdkPojo> pojoSupplier)123 protected <T extends AwsResponse> Function<AwsXmlUnmarshallingContext, T> createResponseTransformer( 124 Supplier<SdkPojo> pojoSupplier) { 125 126 return new AwsXmlResponseTransformer<>( 127 XML_PROTOCOL_UNMARSHALLER, r -> pojoSupplier.get()); 128 } 129 createErrorTransformer()130 protected Function<AwsXmlUnmarshallingContext, AwsServiceException> createErrorTransformer() { 131 return AwsXmlErrorTransformer.builder() 132 .defaultExceptionSupplier(defaultServiceExceptionSupplier) 133 .exceptions(modeledExceptions) 134 .errorUnmarshaller(XML_PROTOCOL_UNMARSHALLER) 135 .build(); 136 } 137 createErrorResponseHandler()138 public HttpResponseHandler<AwsServiceException> createErrorResponseHandler() { 139 return errorUnmarshaller; 140 } 141 timeUnmarshalling(HttpResponseHandler<T> delegate)142 private <T> MetricCollectingHttpResponseHandler<T> timeUnmarshalling(HttpResponseHandler<T> delegate) { 143 return MetricCollectingHttpResponseHandler.create(CoreMetric.UNMARSHALLING_DURATION, delegate); 144 } 145 createCombinedResponseHandler( Supplier<SdkPojo> pojoSupplier, XmlOperationMetadata staxOperationMetadata)146 public <T extends AwsResponse> HttpResponseHandler<Response<T>> createCombinedResponseHandler( 147 Supplier<SdkPojo> pojoSupplier, XmlOperationMetadata staxOperationMetadata) { 148 149 return new CombinedResponseHandler<>(createResponseHandler(pojoSupplier, staxOperationMetadata), 150 createErrorResponseHandler()); 151 } 152 153 /** 154 * Extracts the <Error/> element from the root XML document. This method is protected as S3 has 155 * a slightly different location. 156 * 157 * @param document Root XML document. 158 * @return If error root is found than a fulfilled {@link Optional}, otherwise an empty one. 159 */ getErrorRoot(XmlElement document)160 Optional<XmlElement> getErrorRoot(XmlElement document) { 161 return document.getOptionalElementByName("Error"); 162 } 163 createGenerator(OperationInfo operationInfo)164 protected XmlGenerator createGenerator(OperationInfo operationInfo) { 165 return operationInfo.hasPayloadMembers() ? 166 XmlGenerator.create(operationInfo.addtionalMetadata(XML_NAMESPACE_ATTRIBUTE), false) : 167 null; 168 } 169 builder()170 public static Builder builder() { 171 return new Builder(); 172 } 173 174 /** 175 * Builder for {@link AwsXmlProtocolFactory}. 176 */ 177 public static class Builder<SubclassT extends Builder> { 178 179 private final List<ExceptionMetadata> modeledExceptions = new ArrayList<>(); 180 private Supplier<SdkPojo> defaultServiceExceptionSupplier; 181 private SdkClientConfiguration clientConfiguration; 182 Builder()183 Builder() { 184 } 185 186 /** 187 * Registers a new modeled exception by the error code. 188 * 189 * @param errorMetadata metadata for unmarshalling the exceptions 190 * @return This builder for method chaining. 191 */ registerModeledException(ExceptionMetadata errorMetadata)192 public final SubclassT registerModeledException(ExceptionMetadata errorMetadata) { 193 modeledExceptions.add(errorMetadata); 194 return getSubclass(); 195 } 196 197 /** 198 * A supplier for the services base exception builder. This is used when we can't identify any modeled 199 * exception to unmarshall into. 200 * 201 * @param exceptionBuilderSupplier Suppplier of the base service exceptions Builder. 202 * @return This builder for method chaining. 203 */ defaultServiceExceptionSupplier(Supplier<SdkPojo> exceptionBuilderSupplier)204 public SubclassT defaultServiceExceptionSupplier(Supplier<SdkPojo> exceptionBuilderSupplier) { 205 this.defaultServiceExceptionSupplier = exceptionBuilderSupplier; 206 return getSubclass(); 207 } 208 209 /** 210 * Sets the {@link SdkClientConfiguration} which contains the service endpoint. 211 * 212 * @param clientConfiguration Configuration of the client. 213 * @return This builder for method chaining. 214 */ clientConfiguration(SdkClientConfiguration clientConfiguration)215 public SubclassT clientConfiguration(SdkClientConfiguration clientConfiguration) { 216 this.clientConfiguration = clientConfiguration; 217 return getSubclass(); 218 } 219 220 @SuppressWarnings("unchecked") getSubclass()221 private SubclassT getSubclass() { 222 return (SubclassT) this; 223 } 224 build()225 public AwsXmlProtocolFactory build() { 226 return new AwsXmlProtocolFactory(this); 227 } 228 } 229 } 230