From 2501c578ad55512fa8b4a9ee608d8bd99523d6e2 Mon Sep 17 00:00:00 2001 From: Krishnan Date: Thu, 14 Mar 2024 10:01:06 -0700 Subject: [PATCH 01/18] Add Warnings in the async client interface and CrtClient against passing in Runnable::run --- .../awssdk/codegen/poet/client/AsyncClientInterface.java | 5 ++++- .../poet/client/test-json-async-client-interface.java | 3 ++- .../awssdk/services/s3/S3CrtAsyncClientBuilder.java | 8 +++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterface.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterface.java index 04f64c1b128c..1dd583eb3353 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterface.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterface.java @@ -169,7 +169,10 @@ public ClassName className() { private String getJavadoc() { return "Service client for accessing " + model.getMetadata().getDescriptiveServiceName() + " asynchronously. This can be " - + "created using the static {@link #builder()} method.\n\n" + model.getMetadata().getDocumentation(); + + "created using the static {@link #builder()} method.\nWe strongly discourage " + + "configuring main I/O threads to run future-completion objects. This could " + + "lead to deadlock as the SDK may perform blocking calls in some cases." + + "\n\n" + model.getMetadata().getDocumentation(); } private MethodSpec create() { diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface.java index ec3f92e7a92d..a376eb7447bf 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface.java @@ -53,7 +53,8 @@ /** * Service client for accessing Json Service asynchronously. This can be created using the static {@link #builder()} - * method. + * method. We strongly discourage configuring main I/O threads to run future-completion objects. This could lead to + * deadlock as the SDK may perform blocking calls in some cases. * * A service that is implemented using the query protocol */ diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java index 23dc6ad7ce51..8005ccd909df 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java @@ -322,11 +322,13 @@ default S3CrtAsyncClientBuilder retryConfiguration(ConsumerYou want more fine-grained control over the {@link ThreadPoolExecutor} used, such as configuring the pool size * or sharing a single pool between multiple clients. *
  • You want to add instrumentation (i.e., metrics) around how the {@link Executor} is used. - *
  • You know, for certain, that all of your {@link CompletableFuture} usage is strictly non-blocking, and you wish to - * remove the minor overhead incurred by using a separate thread. In this case, you can use - * {@code Runnable::run} to execute the future-completion directly from within the I/O thread. * * + * WARNING + * We strongly discourage using {@code Runnable::run}, which executes the future-completion directly from + * within the I/O thread because it may block the I/O thread and cause deadlock, especially if you are sending + * another SDK request in the {@link CompletableFuture} chain since the SDK may perform blocking calls in some cases. + * * @param futureCompletionExecutor the executor * @return an instance of this builder. */ From 273ce0fa52bf7a134b425576fe45b335fd3c712c Mon Sep 17 00:00:00 2001 From: Krishnan Date: Thu, 14 Mar 2024 11:17:18 -0700 Subject: [PATCH 02/18] Update warning message --- .../poet/client/test-json-async-client-interface.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface.java index a376eb7447bf..c8c8d794e53b 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface.java @@ -53,8 +53,9 @@ /** * Service client for accessing Json Service asynchronously. This can be created using the static {@link #builder()} - * method. We strongly discourage configuring main I/O threads to run future-completion objects. This could lead to - * deadlock as the SDK may perform blocking calls in some cases. + * method. The asynchronous client performs non-blocking I/O when configured with any {@link SdkAsyncHttpClient} + * supported in the SDK. However, full non-blocking is not guaranteed as the async client may perform + * blocking calls in some cases such as credentials retreival and endpoint discovery as part of the async API call. * * A service that is implemented using the query protocol */ From 5c22f5cb156941bb142b92ab8e90ccc337acab49 Mon Sep 17 00:00:00 2001 From: Krishnan Date: Thu, 14 Mar 2024 11:31:17 -0700 Subject: [PATCH 03/18] Updated warning message --- .../awssdk/codegen/poet/client/AsyncClientInterface.java | 9 ++++++--- .../poet/client/test-json-async-client-interface.java | 6 +++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterface.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterface.java index 1dd583eb3353..c9f6b1fc6edd 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterface.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterface.java @@ -169,9 +169,12 @@ public ClassName className() { private String getJavadoc() { return "Service client for accessing " + model.getMetadata().getDescriptiveServiceName() + " asynchronously. This can be " - + "created using the static {@link #builder()} method.\nWe strongly discourage " - + "configuring main I/O threads to run future-completion objects. This could " - + "lead to deadlock as the SDK may perform blocking calls in some cases." + + "created using the static {@link #builder()} method." + + "The asynchronous client performs non-blocking I/O when configured " + + "with any {@link SdkAsyncHttpClient} supported in the SDK. " + + "However, full non-blocking is not guaranteed as the async client may perform " + + "blocking calls in some cases such as credentials retrieval and " + + "endpoint discovery as part of the async API call." + "\n\n" + model.getMetadata().getDocumentation(); } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface.java index c8c8d794e53b..6508fa3bc790 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface.java @@ -53,9 +53,9 @@ /** * Service client for accessing Json Service asynchronously. This can be created using the static {@link #builder()} - * method. The asynchronous client performs non-blocking I/O when configured with any {@link SdkAsyncHttpClient} - * supported in the SDK. However, full non-blocking is not guaranteed as the async client may perform - * blocking calls in some cases such as credentials retreival and endpoint discovery as part of the async API call. + * method.The asynchronous client performs non-blocking I/O when configured with any {@link SdkAsyncHttpClient} + * supported in the SDK. However, full non-blocking is not guaranteed as the async client may perform blocking calls in + * some cases such as credentials retrieval and endpoint discovery as part of the async API call. * * A service that is implemented using the query protocol */ From 10ae28e467e2bad22d76aa0a3592778b1e7abab8 Mon Sep 17 00:00:00 2001 From: Krishnan Date: Mon, 25 Mar 2024 10:39:48 -0700 Subject: [PATCH 04/18] Added paragraph notation --- .../amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java index 8005ccd909df..6a726506308b 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java @@ -323,12 +323,12 @@ default S3CrtAsyncClientBuilder retryConfiguration(ConsumerYou want to add instrumentation (i.e., metrics) around how the {@link Executor} is used. * - * + *

    * WARNING * We strongly discourage using {@code Runnable::run}, which executes the future-completion directly from * within the I/O thread because it may block the I/O thread and cause deadlock, especially if you are sending * another SDK request in the {@link CompletableFuture} chain since the SDK may perform blocking calls in some cases. - * + *

    * @param futureCompletionExecutor the executor * @return an instance of this builder. */ From 5ab3decc82e903792f45fd071bb70a6e380f52c6 Mon Sep 17 00:00:00 2001 From: Krishnan Date: Mon, 25 Mar 2024 10:49:02 -0700 Subject: [PATCH 05/18] Add a paragraph notation --- .../amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java index 6a726506308b..cd5cf77554cb 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java @@ -328,7 +328,7 @@ default S3CrtAsyncClientBuilder retryConfiguration(Consumerdiscourage using {@code Runnable::run}, which executes the future-completion directly from * within the I/O thread because it may block the I/O thread and cause deadlock, especially if you are sending * another SDK request in the {@link CompletableFuture} chain since the SDK may perform blocking calls in some cases. - *

    + * * @param futureCompletionExecutor the executor * @return an instance of this builder. */ From 512dee955e4de3026cbcc921078f2b0e76affd3e Mon Sep 17 00:00:00 2001 From: Krishnan Date: Fri, 29 Mar 2024 10:00:09 -0700 Subject: [PATCH 06/18] Modify S3 expires field to not fail on parsing errors and instead, log warnings. Also modified the Expires field to be a timestamp --- .../unmarshall/XmlProtocolUnmarshaller.java | 12 +++- .../codegen-resources/customization.config | 72 +++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/XmlProtocolUnmarshaller.java b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/XmlProtocolUnmarshaller.java index 56d950f917eb..a6a8e4cf3373 100644 --- a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/XmlProtocolUnmarshaller.java +++ b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/XmlProtocolUnmarshaller.java @@ -20,10 +20,13 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.time.Instant; +import java.time.format.DateTimeParseException; import java.util.Collections; import java.util.EnumMap; import java.util.List; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.core.SdkField; @@ -44,6 +47,7 @@ @SdkInternalApi public final class XmlProtocolUnmarshaller implements XmlErrorUnmarshaller { + private static final Logger log = LoggerFactory.getLogger(XmlProtocolUnmarshaller.class); public static final StringToValueConverter.StringToValue INSTANT_STRING_TO_VALUE = StringToInstant.create(getDefaultTimestampFormats()); @@ -82,8 +86,12 @@ SdkPojo unmarshall(XmlUnmarshallerContext context, SdkPojo sdkPojo, XmlElement r XmlUnmarshaller unmarshaller = REGISTRY.getUnmarshaller(field.location(), field.marshallingType()); if (field.location() != MarshallLocation.PAYLOAD) { - Object unmarshalled = unmarshaller.unmarshall(context, null, (SdkField) field); - field.set(sdkPojo, unmarshalled); + try { + Object unmarshalled = unmarshaller.unmarshall(context, null, (SdkField) field); + field.set(sdkPojo, unmarshalled); + } catch (DateTimeParseException e) { + log.warn("Invalid datetime format provided in the Expires field {}", e.getParsedString()); + } continue; } diff --git a/services/s3/src/main/resources/codegen-resources/customization.config b/services/s3/src/main/resources/codegen-resources/customization.config index 0f06cc755691..fd808f4d3fed 100644 --- a/services/s3/src/main/resources/codegen-resources/customization.config +++ b/services/s3/src/main/resources/codegen-resources/customization.config @@ -27,6 +27,78 @@ } ] }, + "PutObjectRequest": { + "modify": [ + { + "Expires": { + "emitAsType": "timestamp" + } + } + ] + }, + "WriteGetObjectResponseRequest": { + "modify": [ + { + "Expires": { + "emitAsType": "timestamp" + } + } + ] + }, + "GetObjectOutput": { + "modify": [ + { + "Expires": { + "emitAsType": "timestamp" + } + } + ] + }, + "WriteGetObjectResponseRequest": { + "modify": [ + { + "Expires": { + "emitAsType": "timestamp" + } + } + ] + }, + "CopyObjectRequest": { + "modify": [ + { + "Expires": { + "emitAsType": "timestamp" + } + } + ] + }, + "HeadObjectOutput": { + "modify": [ + { + "Expires": { + "emitAsType": "timestamp" + } + } + ] + }, + "WriteGetObjectResponseRequest": { + "modify": [ + { + "Expires": { + "emitAsType": "timestamp" + } + } + ] + }, + "CreateMultipartUploadRequest": { + "modify": [ + { + "Expires": { + "emitAsType": "timestamp" + } + } + ] + }, "UploadPartRequest": { "inject": [ { From 138afae36e596c8f22f8f98d1cd1da48addd9637 Mon Sep 17 00:00:00 2001 From: Krishnan Date: Fri, 29 Mar 2024 13:57:22 -0700 Subject: [PATCH 07/18] Fix checkstyle issues --- .../xml/internal/unmarshall/XmlProtocolUnmarshaller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/XmlProtocolUnmarshaller.java b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/XmlProtocolUnmarshaller.java index a6a8e4cf3373..18f21c0efd96 100644 --- a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/XmlProtocolUnmarshaller.java +++ b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/XmlProtocolUnmarshaller.java @@ -47,11 +47,11 @@ @SdkInternalApi public final class XmlProtocolUnmarshaller implements XmlErrorUnmarshaller { - private static final Logger log = LoggerFactory.getLogger(XmlProtocolUnmarshaller.class); public static final StringToValueConverter.StringToValue INSTANT_STRING_TO_VALUE = StringToInstant.create(getDefaultTimestampFormats()); private static final XmlUnmarshallerRegistry REGISTRY = createUnmarshallerRegistry(); + private static final Logger log = LoggerFactory.getLogger(XmlProtocolUnmarshaller.class); private XmlProtocolUnmarshaller() { } From a5d94d523d29a4961fd6f37823f6f5f17512a3bc Mon Sep 17 00:00:00 2001 From: Krishnan Date: Fri, 29 Mar 2024 14:36:49 -0700 Subject: [PATCH 08/18] Fixed duplicates in customization.config definition --- .../codegen-resources/customization.config | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/services/s3/src/main/resources/codegen-resources/customization.config b/services/s3/src/main/resources/codegen-resources/customization.config index fd808f4d3fed..a6785f69e9a2 100644 --- a/services/s3/src/main/resources/codegen-resources/customization.config +++ b/services/s3/src/main/resources/codegen-resources/customization.config @@ -54,15 +54,6 @@ } ] }, - "WriteGetObjectResponseRequest": { - "modify": [ - { - "Expires": { - "emitAsType": "timestamp" - } - } - ] - }, "CopyObjectRequest": { "modify": [ { @@ -81,15 +72,6 @@ } ] }, - "WriteGetObjectResponseRequest": { - "modify": [ - { - "Expires": { - "emitAsType": "timestamp" - } - } - ] - }, "CreateMultipartUploadRequest": { "modify": [ { From 9142048e9982f10f303fabd1a4d7ee72a784e373 Mon Sep 17 00:00:00 2001 From: Krishnan Date: Sat, 30 Mar 2024 21:11:10 -0700 Subject: [PATCH 09/18] Remove duplicate definitions --- .../codegen-resources/customization.config | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/services/s3/src/main/resources/codegen-resources/customization.config b/services/s3/src/main/resources/codegen-resources/customization.config index a6785f69e9a2..20edfb26976e 100644 --- a/services/s3/src/main/resources/codegen-resources/customization.config +++ b/services/s3/src/main/resources/codegen-resources/customization.config @@ -54,15 +54,6 @@ } ] }, - "CopyObjectRequest": { - "modify": [ - { - "Expires": { - "emitAsType": "timestamp" - } - } - ] - }, "HeadObjectOutput": { "modify": [ { @@ -121,7 +112,11 @@ "Key": { "emitPropertyName": "DestinationKey", "existingNameDeprecated": true + }, + "Expires": { + "emitAsType": "timestamp" } + } ] }, From a57a37849ea65ea58080ef0dde4604c91a9b26e9 Mon Sep 17 00:00:00 2001 From: Krishnan Date: Thu, 25 Apr 2024 02:45:04 -0700 Subject: [PATCH 10/18] Facilitate custom handling of data type conversion errors --- .../processors/ShapeModifiersProcessor.java | 5 ++ .../ModifyModelShapeModifier.java | 13 +++++ .../model/intermediate/MemberModel.java | 10 ++++ .../codegen/poet/model/ShapeModelSpec.java | 11 ++++ .../unmarshall/HeaderUnmarshaller.java | 18 +++++- .../unmarshall/XmlProtocolUnmarshaller.java | 12 +--- .../software/amazon/awssdk/core/SdkField.java | 11 ++++ .../metrics/BytesReadTrackingPublisher.java | 1 + ...ataTypeConversionFailureHandlingTrait.java | 22 ++++++++ .../codegen-resources/customization.config | 30 +++++++++- .../services/s3/ExpiresHeaderParsingTest.java | 56 +++++++++++++++++++ 11 files changed, 174 insertions(+), 15 deletions(-) create mode 100644 core/sdk-core/src/main/java/software/amazon/awssdk/core/traits/DataTypeConversionFailureHandlingTrait.java create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderParsingTest.java diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/customization/processors/ShapeModifiersProcessor.java b/codegen/src/main/java/software/amazon/awssdk/codegen/customization/processors/ShapeModifiersProcessor.java index d4e1b02d5639..a1b03a6b60d8 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/customization/processors/ShapeModifiersProcessor.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/customization/processors/ShapeModifiersProcessor.java @@ -160,6 +160,11 @@ private void postprocessModifyMemberProperty(ShapeModel shapeModel, String membe .getUnmarshallLocationName()); } + if (modifyModel.isIgnoreDataTypeConversionFailures()) { + MemberModel memberModel = shapeModel.findMemberModelByC2jName(memberName); + memberModel.ignoreDataTypeConversionFailures(true); + } + } /** diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/ModifyModelShapeModifier.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/ModifyModelShapeModifier.java index dffaf7d0d40b..468ec7154fa3 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/ModifyModelShapeModifier.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/ModifyModelShapeModifier.java @@ -59,6 +59,11 @@ public class ModifyModelShapeModifier { private String unmarshallLocationName; + /** + * Indicates whether data type conversion failures are to be ignored + */ + private boolean ignoreDataTypeConversionFailures; + public String getDeprecatedMessage() { return deprecatedMessage; } @@ -130,4 +135,12 @@ public String getEmitAsType() { public void setEmitAsType(String emitAsType) { this.emitAsType = emitAsType; } + + public void setIgnoreDataTypeConversionFailures(boolean ignoreDataTypeConversionFailures) { + this.ignoreDataTypeConversionFailures = ignoreDataTypeConversionFailures; + } + + public boolean isIgnoreDataTypeConversionFailures() { + return ignoreDataTypeConversionFailures; + } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/MemberModel.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/MemberModel.java index f6a870409ebd..fddf93d4d72d 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/MemberModel.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/MemberModel.java @@ -115,6 +115,8 @@ public class MemberModel extends DocumentationModel { private ContextParam contextParam; + private boolean ignoreDataTypeConversionFailures; + public String getName() { return name; } @@ -775,4 +777,12 @@ public ContextParam getContextParam() { public void setContextParam(ContextParam contextParam) { this.contextParam = contextParam; } + + public void ignoreDataTypeConversionFailures(boolean ignoreDataTypeConversionFailures) { + this.ignoreDataTypeConversionFailures = ignoreDataTypeConversionFailures; + } + + public boolean ignoreDataTypeConversionFailures() { + return ignoreDataTypeConversionFailures; + } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ShapeModelSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ShapeModelSpec.java index 8d79d83928ae..d10ab65a8a27 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ShapeModelSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ShapeModelSpec.java @@ -39,6 +39,7 @@ import software.amazon.awssdk.core.SdkField; import software.amazon.awssdk.core.protocol.MarshallLocation; import software.amazon.awssdk.core.protocol.MarshallingType; +import software.amazon.awssdk.core.traits.DataTypeConversionFailureHandlingTrait; import software.amazon.awssdk.core.traits.DefaultValueTrait; import software.amazon.awssdk.core.traits.JsonValueTrait; import software.amazon.awssdk.core.traits.ListTrait; @@ -186,6 +187,10 @@ private CodeBlock traits(MemberModel m) { if (m.isXmlAttribute()) { traits.add(createXmlAttributeTrait()); } + + if (m.ignoreDataTypeConversionFailures()) { + traits.add(createDataTypeConversionFailureHandlingTrait()); + } if (customizationConfig.isRequiredTraitValidationEnabled() && m.isRequired()) { traits.add(createRequiredTrait()); } @@ -206,6 +211,12 @@ private boolean attachPayloadTraitToMember(MemberModel m) { .equals(m.getC2jName()); } + private CodeBlock createDataTypeConversionFailureHandlingTrait() { + return CodeBlock.builder() + .add("new $T()", ClassName.get(DataTypeConversionFailureHandlingTrait.class)) + .build(); + } + private CodeBlock createTimestampFormatTrait(MemberModel m) { TimestampFormatTrait.Format format = TimestampFormatTrait.Format.fromString(m.getTimestampFormat()); ClassName traitClass = ClassName.get(TimestampFormatTrait.class); diff --git a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java index fe541ad104a8..811ec10b997b 100644 --- a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java +++ b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java @@ -22,6 +22,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.core.SdkField; import software.amazon.awssdk.protocols.core.StringToValueConverter; @@ -61,6 +63,8 @@ private HeaderUnmarshaller() { private static class SimpleHeaderUnmarshaller implements XmlUnmarshaller { + private static final Logger log = LoggerFactory.getLogger(SimpleHeaderUnmarshaller.class); + private final StringToValueConverter.StringToValue stringToValue; private SimpleHeaderUnmarshaller(StringToValueConverter.StringToValue stringToValue) { @@ -69,9 +73,17 @@ private SimpleHeaderUnmarshaller(StringToValueConverter.StringToValue stringT @Override public T unmarshall(XmlUnmarshallerContext context, List content, SdkField field) { - return context.response().firstMatchingHeader(field.locationName()) - .map(s -> stringToValue.convert(s, field)) - .orElse(null); + try { + return context.response().firstMatchingHeader(field.locationName()) + .map(s -> stringToValue.convert(s, field)) + .orElse(null); + } catch (RuntimeException e) { + log.warn("Exception found while parsing response header {}", e.getMessage()); + if(field.ignoreDataTypeConversionFailures()) { + return null; + } + throw e; + } } } } diff --git a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/XmlProtocolUnmarshaller.java b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/XmlProtocolUnmarshaller.java index 18f21c0efd96..56d950f917eb 100644 --- a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/XmlProtocolUnmarshaller.java +++ b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/XmlProtocolUnmarshaller.java @@ -20,13 +20,10 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.time.Instant; -import java.time.format.DateTimeParseException; import java.util.Collections; import java.util.EnumMap; import java.util.List; import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.core.SdkField; @@ -51,7 +48,6 @@ public final class XmlProtocolUnmarshaller implements XmlErrorUnmarshaller { public static final StringToValueConverter.StringToValue INSTANT_STRING_TO_VALUE = StringToInstant.create(getDefaultTimestampFormats()); private static final XmlUnmarshallerRegistry REGISTRY = createUnmarshallerRegistry(); - private static final Logger log = LoggerFactory.getLogger(XmlProtocolUnmarshaller.class); private XmlProtocolUnmarshaller() { } @@ -86,12 +82,8 @@ SdkPojo unmarshall(XmlUnmarshallerContext context, SdkPojo sdkPojo, XmlElement r XmlUnmarshaller unmarshaller = REGISTRY.getUnmarshaller(field.location(), field.marshallingType()); if (field.location() != MarshallLocation.PAYLOAD) { - try { - Object unmarshalled = unmarshaller.unmarshall(context, null, (SdkField) field); - field.set(sdkPojo, unmarshalled); - } catch (DateTimeParseException e) { - log.warn("Invalid datetime format provided in the Expires field {}", e.getParsedString()); - } + Object unmarshalled = unmarshaller.unmarshall(context, null, (SdkField) field); + field.set(sdkPojo, unmarshalled); continue; } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkField.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkField.java index 161754efa1f7..063ef85f2cd6 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkField.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkField.java @@ -25,6 +25,7 @@ import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.core.protocol.MarshallLocation; import software.amazon.awssdk.core.protocol.MarshallingType; +import software.amazon.awssdk.core.traits.DataTypeConversionFailureHandlingTrait; import software.amazon.awssdk.core.traits.DefaultValueTrait; import software.amazon.awssdk.core.traits.LocationTrait; import software.amazon.awssdk.core.traits.Trait; @@ -86,6 +87,16 @@ public String locationName() { return locationName; } + /** + * @return whether data-type conversion errors are to be ignored + */ + public boolean ignoreDataTypeConversionFailures() { + DataTypeConversionFailureHandlingTrait dataTypeConversionFailureHandlingTrait = + getTrait(DataTypeConversionFailureHandlingTrait.class); + + return dataTypeConversionFailureHandlingTrait != null; + } + /** * @return The location name to use when unmarshalling. This is only needed for AWS/Query or EC2 services. All * other services should use {@link #locationName} for both marshalling and unmarshalling. diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/metrics/BytesReadTrackingPublisher.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/metrics/BytesReadTrackingPublisher.java index dd8ef03b7312..6ee52d37ac6d 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/metrics/BytesReadTrackingPublisher.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/metrics/BytesReadTrackingPublisher.java @@ -55,6 +55,7 @@ private BytesReadTracker(Subscriber downstream, AtomicLong b @Override public void onSubscribe(Subscription subscription) { + //resetBytesSent/Received downstream.onSubscribe(subscription); } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/traits/DataTypeConversionFailureHandlingTrait.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/traits/DataTypeConversionFailureHandlingTrait.java new file mode 100644 index 000000000000..243ff2dac30b --- /dev/null +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/traits/DataTypeConversionFailureHandlingTrait.java @@ -0,0 +1,22 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.core.traits; + +import software.amazon.awssdk.annotations.SdkProtectedApi; + +@SdkProtectedApi +public class DataTypeConversionFailureHandlingTrait implements Trait { +} diff --git a/services/s3/src/main/resources/codegen-resources/customization.config b/services/s3/src/main/resources/codegen-resources/customization.config index 20edfb26976e..0e573b1c8612 100644 --- a/services/s3/src/main/resources/codegen-resources/customization.config +++ b/services/s3/src/main/resources/codegen-resources/customization.config @@ -49,7 +49,20 @@ "modify": [ { "Expires": { - "emitAsType": "timestamp" + "emitAsType": "timestamp", + "deprecated": "true", + "deprecatedMessage": "Usage of the Expires field is deprecated in favor of the more flexible and less error prone ExpiresString", + "ignoreDataTypeConversionFailures": "true" + } + } + ], + "inject": [ + { + "ExpiresString": { + "shape": "Expiration", + "documentation":"

    The date and time at which the object is no longer cacheable

    ", + "location":"header", + "locationName":"Expires" } } ] @@ -58,7 +71,20 @@ "modify": [ { "Expires": { - "emitAsType": "timestamp" + "emitAsType": "timestamp", + "deprecated": "true", + "deprecatedMessage": "Usage of the Expires field is deprecated in favor of the more flexible and less error prone ExpiresString", + "ignoreDataTypeConversionFailures": "true" + } + } + ], + "inject": [ + { + "ExpiresString": { + "shape": "Expiration", + "documentation":"

    The date and time at which the object is no longer cacheable

    ", + "location":"header", + "locationName":"Expires" } } ] diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderParsingTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderParsingTest.java new file mode 100644 index 000000000000..8693cf9ad4b1 --- /dev/null +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderParsingTest.java @@ -0,0 +1,56 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.services.s3; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.List; +import org.apache.logging.log4j.core.LogEvent; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.s3.model.HeadObjectRequest; +import software.amazon.awssdk.testutils.LogCaptor; + +public class ExpiresHeaderParsingTest { + + @Test + void test() throws IOException { + + String bucketName = "anirudkr-bucket-2"; + String key = "key1.txt"; + + //File file = new RandomTempFile(10_000); + S3Client client = S3Client.builder().build(); + + //PutObjectRequest put = PutObjectRequest.builder().bucket(bucketName).key(key).expires(Instant.now()).build(); + //client.putObject(put, file.toPath()); + + LogCaptor logCaptor = LogCaptor.create(); + + HeadObjectRequest request = HeadObjectRequest.builder().bucket(bucketName).key(key).build(); + Assertions.assertThatCode(() -> client.headObject(request)) + .doesNotThrowAnyException(); + assertTrue(client.headObject(request).expires() == null); + assertTrue(client.headObject(request).expiresString() != null); + //assertEquals(client.headObject(request).expires().toString(), client.headObject(request).expiresString()); + + List events = logCaptor.loggedEvents(); + //assertLogged(events, Level.WARN, "Invalid datetime format provided in the Expires field 2034-01-01T00:00:00Z"); + String expected = "Invalid datetime format provided in the Expires field 2034-01-01T00:00:00Z"; + //assertTrue(events.stream().filter(s -> s.getMessage().getFormattedMessage().equals(expected)).collect(Collectors.toList()).isEmpty()); + } +} From af0aa2df44f87db0223386367bf0348d681535d8 Mon Sep 17 00:00:00 2001 From: Krishnan Date: Thu, 25 Apr 2024 02:48:47 -0700 Subject: [PATCH 11/18] Removed test class --- .../services/s3/ExpiresHeaderParsingTest.java | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderParsingTest.java diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderParsingTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderParsingTest.java deleted file mode 100644 index 8693cf9ad4b1..000000000000 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderParsingTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package software.amazon.awssdk.services.s3; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.util.List; -import org.apache.logging.log4j.core.LogEvent; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; -import software.amazon.awssdk.services.s3.model.HeadObjectRequest; -import software.amazon.awssdk.testutils.LogCaptor; - -public class ExpiresHeaderParsingTest { - - @Test - void test() throws IOException { - - String bucketName = "anirudkr-bucket-2"; - String key = "key1.txt"; - - //File file = new RandomTempFile(10_000); - S3Client client = S3Client.builder().build(); - - //PutObjectRequest put = PutObjectRequest.builder().bucket(bucketName).key(key).expires(Instant.now()).build(); - //client.putObject(put, file.toPath()); - - LogCaptor logCaptor = LogCaptor.create(); - - HeadObjectRequest request = HeadObjectRequest.builder().bucket(bucketName).key(key).build(); - Assertions.assertThatCode(() -> client.headObject(request)) - .doesNotThrowAnyException(); - assertTrue(client.headObject(request).expires() == null); - assertTrue(client.headObject(request).expiresString() != null); - //assertEquals(client.headObject(request).expires().toString(), client.headObject(request).expiresString()); - - List events = logCaptor.loggedEvents(); - //assertLogged(events, Level.WARN, "Invalid datetime format provided in the Expires field 2034-01-01T00:00:00Z"); - String expected = "Invalid datetime format provided in the Expires field 2034-01-01T00:00:00Z"; - //assertTrue(events.stream().filter(s -> s.getMessage().getFormattedMessage().equals(expected)).collect(Collectors.toList()).isEmpty()); - } -} From 4dc79bfb18d33f2e9cdaf017f88b65805224cc61 Mon Sep 17 00:00:00 2001 From: Krishnan Date: Thu, 25 Apr 2024 02:51:13 -0700 Subject: [PATCH 12/18] Remove incorrectly added statements --- .../awssdk/core/internal/metrics/BytesReadTrackingPublisher.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/metrics/BytesReadTrackingPublisher.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/metrics/BytesReadTrackingPublisher.java index 6ee52d37ac6d..dd8ef03b7312 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/metrics/BytesReadTrackingPublisher.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/metrics/BytesReadTrackingPublisher.java @@ -55,7 +55,6 @@ private BytesReadTracker(Subscriber downstream, AtomicLong b @Override public void onSubscribe(Subscription subscription) { - //resetBytesSent/Received downstream.onSubscribe(subscription); } From a6824f3c72472c84ad055e95337cb7f726168991 Mon Sep 17 00:00:00 2001 From: Krishnan Date: Mon, 29 Apr 2024 04:02:12 -0700 Subject: [PATCH 13/18] Added Unit tests --- .../codegen/poet/model/customization.config | 5 + .../operationwithdeprecatedmemberrequest.java | 59 +++++++++++- .../awssdk/codegen/poet/model/service-2.json | 3 +- .../unmarshall/HeaderUnmarshaller.java | 13 ++- .../s3/ExpiresHeaderDataTypeErrorTest.java | 96 +++++++++++++++++++ 5 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/customization.config index 213183ce5a87..0375c733e912 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/customization.config +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/customization.config @@ -26,6 +26,11 @@ "deprecated": true, "deprecatedMessage": "This field is modified as deprecated." } + }, + { + "MemberIgnoreDataTypeFailureHandling": { + "ignoreDataTypeConversionFailures": "true" + } } ] }, diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/operationwithdeprecatedmemberrequest.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/operationwithdeprecatedmemberrequest.java index 05cf3b39317c..c14ae6ee3ede 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/operationwithdeprecatedmemberrequest.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/operationwithdeprecatedmemberrequest.java @@ -14,6 +14,7 @@ import software.amazon.awssdk.core.SdkPojo; import software.amazon.awssdk.core.protocol.MarshallLocation; import software.amazon.awssdk.core.protocol.MarshallingType; +import software.amazon.awssdk.core.traits.DataTypeConversionFailureHandlingTrait; import software.amazon.awssdk.core.traits.LocationTrait; import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.builder.CopyableBuilder; @@ -45,8 +46,18 @@ public final class OperationWithDeprecatedMemberRequest extends JsonProtocolTest .traits(LocationTrait.builder().location(MarshallLocation.PAYLOAD).locationName("UndeprecatedMember").build()) .build(); + private static final SdkField MEMBER_IGNORE_DATA_TYPE_FAILURE_HANDLING_FIELD = SdkField + . builder(MarshallingType.STRING) + .memberName("MemberIgnoreDataTypeFailureHandling") + .getter(getter(OperationWithDeprecatedMemberRequest::memberIgnoreDataTypeFailureHandling)) + .setter(setter(Builder::memberIgnoreDataTypeFailureHandling)) + .traits(LocationTrait.builder().location(MarshallLocation.PAYLOAD) + .locationName("MemberIgnoreDataTypeFailureHandling").build(), new DataTypeConversionFailureHandlingTrait()) + .build(); + private static final List> SDK_FIELDS = Collections.unmodifiableList(Arrays.asList( - MEMBER_MODELED_AS_DEPRECATED_FIELD, MEMBER_MODIFIED_AS_DEPRECATED_FIELD, UNDEPRECATED_MEMBER_FIELD)); + MEMBER_MODELED_AS_DEPRECATED_FIELD, MEMBER_MODIFIED_AS_DEPRECATED_FIELD, UNDEPRECATED_MEMBER_FIELD, + MEMBER_IGNORE_DATA_TYPE_FAILURE_HANDLING_FIELD)); private final String memberModeledAsDeprecated; @@ -54,11 +65,14 @@ public final class OperationWithDeprecatedMemberRequest extends JsonProtocolTest private final String undeprecatedMember; + private final String memberIgnoreDataTypeFailureHandling; + private OperationWithDeprecatedMemberRequest(BuilderImpl builder) { super(builder); this.memberModeledAsDeprecated = builder.memberModeledAsDeprecated; this.memberModifiedAsDeprecated = builder.memberModifiedAsDeprecated; this.undeprecatedMember = builder.undeprecatedMember; + this.memberIgnoreDataTypeFailureHandling = builder.memberIgnoreDataTypeFailureHandling; } /** @@ -92,6 +106,15 @@ public final String undeprecatedMember() { return undeprecatedMember; } + /** + * Returns the value of the MemberIgnoreDataTypeFailureHandling property for this object. + * + * @return The value of the MemberIgnoreDataTypeFailureHandling property for this object. + */ + public final String memberIgnoreDataTypeFailureHandling() { + return memberIgnoreDataTypeFailureHandling; + } + @Override public Builder toBuilder() { return new BuilderImpl(this); @@ -112,6 +135,7 @@ public final int hashCode() { hashCode = 31 * hashCode + Objects.hashCode(memberModeledAsDeprecated()); hashCode = 31 * hashCode + Objects.hashCode(memberModifiedAsDeprecated()); hashCode = 31 * hashCode + Objects.hashCode(undeprecatedMember()); + hashCode = 31 * hashCode + Objects.hashCode(memberIgnoreDataTypeFailureHandling()); return hashCode; } @@ -134,7 +158,8 @@ public final boolean equalsBySdkFields(Object obj) { OperationWithDeprecatedMemberRequest other = (OperationWithDeprecatedMemberRequest) obj; return Objects.equals(memberModeledAsDeprecated(), other.memberModeledAsDeprecated()) && Objects.equals(memberModifiedAsDeprecated(), other.memberModifiedAsDeprecated()) - && Objects.equals(undeprecatedMember(), other.undeprecatedMember()); + && Objects.equals(undeprecatedMember(), other.undeprecatedMember()) + && Objects.equals(memberIgnoreDataTypeFailureHandling(), other.memberIgnoreDataTypeFailureHandling()); } /** @@ -146,7 +171,7 @@ public final String toString() { return ToString.builder("OperationWithDeprecatedMemberRequest") .add("MemberModeledAsDeprecated", memberModeledAsDeprecated()) .add("MemberModifiedAsDeprecated", memberModifiedAsDeprecated()).add("UndeprecatedMember", undeprecatedMember()) - .build(); + .add("MemberIgnoreDataTypeFailureHandling", memberIgnoreDataTypeFailureHandling()).build(); } public final Optional getValueForField(String fieldName, Class clazz) { @@ -157,6 +182,8 @@ public final Optional getValueForField(String fieldName, Class clazz) return Optional.ofNullable(clazz.cast(memberModifiedAsDeprecated())); case "UndeprecatedMember": return Optional.ofNullable(clazz.cast(undeprecatedMember())); + case "MemberIgnoreDataTypeFailureHandling": + return Optional.ofNullable(clazz.cast(memberIgnoreDataTypeFailureHandling())); default: return Optional.empty(); } @@ -208,6 +235,15 @@ public interface Builder extends JsonProtocolTestsRequest.Builder, SdkPojo, */ Builder undeprecatedMember(String undeprecatedMember); + /** + * Sets the value of the MemberIgnoreDataTypeFailureHandling property for this object. + * + * @param memberIgnoreDataTypeFailureHandling + * The new value for the MemberIgnoreDataTypeFailureHandling property for this object. + * @return Returns a reference to this object so that method calls can be chained together. + */ + Builder memberIgnoreDataTypeFailureHandling(String memberIgnoreDataTypeFailureHandling); + @Override Builder overrideConfiguration(AwsRequestOverrideConfiguration overrideConfiguration); @@ -222,6 +258,8 @@ static final class BuilderImpl extends JsonProtocolTestsRequest.BuilderImpl impl private String undeprecatedMember; + private String memberIgnoreDataTypeFailureHandling; + private BuilderImpl() { } @@ -230,6 +268,7 @@ private BuilderImpl(OperationWithDeprecatedMemberRequest model) { memberModeledAsDeprecated(model.memberModeledAsDeprecated); memberModifiedAsDeprecated(model.memberModifiedAsDeprecated); undeprecatedMember(model.undeprecatedMember); + memberIgnoreDataTypeFailureHandling(model.memberIgnoreDataTypeFailureHandling); } @Deprecated @@ -280,6 +319,20 @@ public final Builder undeprecatedMember(String undeprecatedMember) { return this; } + public final String getMemberIgnoreDataTypeFailureHandling() { + return memberIgnoreDataTypeFailureHandling; + } + + public final void setMemberIgnoreDataTypeFailureHandling(String memberIgnoreDataTypeFailureHandling) { + this.memberIgnoreDataTypeFailureHandling = memberIgnoreDataTypeFailureHandling; + } + + @Override + public final Builder memberIgnoreDataTypeFailureHandling(String memberIgnoreDataTypeFailureHandling) { + this.memberIgnoreDataTypeFailureHandling = memberIgnoreDataTypeFailureHandling; + return this; + } + @Override public Builder overrideConfiguration(AwsRequestOverrideConfiguration overrideConfiguration) { super.overrideConfiguration(overrideConfiguration); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/service-2.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/service-2.json index fb3e0a548d9e..e846f69f04ed 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/service-2.json +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/service-2.json @@ -247,7 +247,8 @@ "deprecatedMessage": "This field is modeled as deprecated." }, "MemberModifiedAsDeprecated":{"shape": "String"}, - "UndeprecatedMember": {"shape": "String"} + "UndeprecatedMember": {"shape": "String"}, + "MemberIgnoreDataTypeFailureHandling":{"shape": "String"} } }, "OperationWithDeprecatedMemberResponse":{ diff --git a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java index 811ec10b997b..edea73b82a4e 100644 --- a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java +++ b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java @@ -22,12 +22,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.core.SdkField; import software.amazon.awssdk.protocols.core.StringToValueConverter; import software.amazon.awssdk.protocols.query.unmarshall.XmlElement; +import software.amazon.awssdk.utils.Logger; @SdkInternalApi public final class HeaderUnmarshaller { @@ -61,13 +60,13 @@ public final class HeaderUnmarshaller { private HeaderUnmarshaller() { } - private static class SimpleHeaderUnmarshaller implements XmlUnmarshaller { + private static class SimpleHeaderUnmarshaller implements XmlUnmarshaller { - private static final Logger log = LoggerFactory.getLogger(SimpleHeaderUnmarshaller.class); + private static final Logger log = Logger.loggerFor(SimpleHeaderUnmarshaller.class); private final StringToValueConverter.StringToValue stringToValue; - private SimpleHeaderUnmarshaller(StringToValueConverter.StringToValue stringToValue) { + private SimpleHeaderUnmarshaller(StringToValueConverter.StringToValue stringToValue) { this.stringToValue = stringToValue; } @@ -78,8 +77,8 @@ public T unmarshall(XmlUnmarshallerContext context, List content, Sd .map(s -> stringToValue.convert(s, field)) .orElse(null); } catch (RuntimeException e) { - log.warn("Exception found while parsing response header {}", e.getMessage()); - if(field.ignoreDataTypeConversionFailures()) { + log.warn(() -> (String.format("Exception found while parsing response header {}", e.getMessage()))); + if (field.ignoreDataTypeConversionFailures()) { return null; } throw e; diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java new file mode 100644 index 000000000000..e1c85aafdab7 --- /dev/null +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java @@ -0,0 +1,96 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.services.s3; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.any; +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import java.io.IOException; +import java.net.URI; +import java.util.List; +import org.apache.logging.log4j.core.LogEvent; +import org.assertj.core.api.Assertions; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; +import software.amazon.awssdk.testutils.LogCaptor; + +public class ExpiresHeaderDataTypeErrorTest { + + @Rule + public WireMockRule wireMock = new WireMockRule(); + + private S3Client s3Client; + + private final String TEST_DATE = "2034-02-01T00:00:00Z"; + @Before + public void setup() { + s3Client = S3Client.builder().endpointOverride(URI.create("http://localhost:" + wireMock.port())) + .credentialsProvider(AnonymousCredentialsProvider.create()) + .build(); + } + + @Test + public void headObjectRequestWithInvalidDate_doesNotThrowException() throws IOException { + + stubFor(any(anyUrl()) + .willReturn(aResponse() + .withHeader("Expires", TEST_DATE) + .withBody("Hello world!"))); + + Assertions.assertThatCode(() -> s3Client.headObject(r -> { + r.bucket("s3_expires_test_dummy_bucket") + .key("s3_expires_test_dummy_key"); + })) + .doesNotThrowAnyException(); + + assertTrue(s3Client.headObject(r -> {r.bucket("s3_expires_test_dummy_bucket") + .key("s3_expires_test_dummy_key");}).expires() == null); + + assertEquals(s3Client.headObject(r -> {r.bucket("s3_expires_test_dummy_bucket") + .key("s3_expires_test_dummy_key");}).expiresString(), TEST_DATE); + + } + + @Test + public void getObjectRequestWithInvalidDate_doesNotThrowException() throws IOException { + + stubFor(any(anyUrl()) + .willReturn(aResponse() + .withHeader("Expires", TEST_DATE) + .withBody("Hello world!"))); + + Assertions.assertThatCode(() -> s3Client.headObject(r -> { + r.bucket("s3_expires_test_dummy_bucket") + .key("s3_expires_test_dummy_key"); + })) + .doesNotThrowAnyException(); + + assertTrue(s3Client.getObject(r -> {r.bucket("s3_expires_test_dummy_bucket") + .key("s3_expires_test_dummy_key");}).response().expires() == null); + + assertEquals(s3Client.getObject(r -> {r.bucket("s3_expires_test_dummy_bucket") + .key("s3_expires_test_dummy_key");}).response().expiresString(), TEST_DATE); + + } +} From fb9d2998946d77d8886cde6aa94d76dab0bca5f1 Mon Sep 17 00:00:00 2001 From: Krishnan Date: Mon, 29 Apr 2024 09:29:08 -0700 Subject: [PATCH 14/18] Changed to Junit5 --- .../s3/ExpiresHeaderDataTypeErrorTest.java | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java index e1c85aafdab7..4bd9e8e0f312 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java @@ -19,45 +19,38 @@ import static com.github.tomakehurst.wiremock.client.WireMock.any; import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; import java.io.IOException; import java.net.URI; -import java.util.List; -import org.apache.logging.log4j.core.LogEvent; import org.assertj.core.api.Assertions; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.Test; import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; -import software.amazon.awssdk.testutils.LogCaptor; +@WireMockTest public class ExpiresHeaderDataTypeErrorTest { - @Rule - public WireMockRule wireMock = new WireMockRule(); - - private S3Client s3Client; - private final String TEST_DATE = "2034-02-01T00:00:00Z"; - @Before - public void setup() { - s3Client = S3Client.builder().endpointOverride(URI.create("http://localhost:" + wireMock.port())) + + public S3Client getS3Client(WireMockRuntimeInfo wm) { + return S3Client.builder().endpointOverride(URI.create(wm.getHttpBaseUrl())) .credentialsProvider(AnonymousCredentialsProvider.create()) .build(); } @Test - public void headObjectRequestWithInvalidDate_doesNotThrowException() throws IOException { + public void headObjectRequestWithInvalidDate_doesNotThrowException(WireMockRuntimeInfo wm) throws IOException { stubFor(any(anyUrl()) .willReturn(aResponse() .withHeader("Expires", TEST_DATE) .withBody("Hello world!"))); + S3Client s3Client = getS3Client(wm); + Assertions.assertThatCode(() -> s3Client.headObject(r -> { r.bucket("s3_expires_test_dummy_bucket") .key("s3_expires_test_dummy_key"); @@ -73,13 +66,15 @@ public void headObjectRequestWithInvalidDate_doesNotThrowException() throws IOEx } @Test - public void getObjectRequestWithInvalidDate_doesNotThrowException() throws IOException { + public void getObjectRequestWithInvalidDate_doesNotThrowException(WireMockRuntimeInfo wm) throws IOException { stubFor(any(anyUrl()) .willReturn(aResponse() .withHeader("Expires", TEST_DATE) .withBody("Hello world!"))); + S3Client s3Client = getS3Client(wm); + Assertions.assertThatCode(() -> s3Client.headObject(r -> { r.bucket("s3_expires_test_dummy_bucket") .key("s3_expires_test_dummy_key"); From e2e52e132f29ddd9fecfb7115215577f87af9ca4 Mon Sep 17 00:00:00 2001 From: Krishnan Date: Mon, 29 Apr 2024 10:06:15 -0700 Subject: [PATCH 15/18] Fixed checkstyle issues --- .../xml/internal/unmarshall/HeaderUnmarshaller.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java index edea73b82a4e..9e7259cfdd64 100644 --- a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java +++ b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java @@ -60,13 +60,12 @@ public final class HeaderUnmarshaller { private HeaderUnmarshaller() { } - private static class SimpleHeaderUnmarshaller implements XmlUnmarshaller { - + private static class SimpleHeaderUnmarshaller implements XmlUnmarshaller { private static final Logger log = Logger.loggerFor(SimpleHeaderUnmarshaller.class); private final StringToValueConverter.StringToValue stringToValue; - private SimpleHeaderUnmarshaller(StringToValueConverter.StringToValue stringToValue) { + private SimpleHeaderUnmarshaller(StringToValueConverter.StringToValue stringToValue) { this.stringToValue = stringToValue; } From db0952d51854cacba91eb9f265b93147512022d7 Mon Sep 17 00:00:00 2001 From: Krishnan Date: Mon, 29 Apr 2024 11:11:20 -0700 Subject: [PATCH 16/18] Added wiremock --- .../s3/ExpiresHeaderDataTypeErrorTest.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java index 4bd9e8e0f312..508b34b0737a 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java @@ -27,30 +27,32 @@ import java.io.IOException; import java.net.URI; import org.assertj.core.api.Assertions; +import org.junit.Before; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; @WireMockTest public class ExpiresHeaderDataTypeErrorTest { + S3Client s3Client; private final String TEST_DATE = "2034-02-01T00:00:00Z"; - public S3Client getS3Client(WireMockRuntimeInfo wm) { - return S3Client.builder().endpointOverride(URI.create(wm.getHttpBaseUrl())) - .credentialsProvider(AnonymousCredentialsProvider.create()) - .build(); + @BeforeEach + public void initWireMock(WireMockRuntimeInfo wm) { + s3Client = S3Client.builder().endpointOverride(URI.create(wm.getHttpBaseUrl())) + .credentialsProvider(AnonymousCredentialsProvider.create()) + .build(); } @Test - public void headObjectRequestWithInvalidDate_doesNotThrowException(WireMockRuntimeInfo wm) throws IOException { + public void headObjectRequestWithInvalidDate_doesNotThrowException() throws IOException { stubFor(any(anyUrl()) .willReturn(aResponse() .withHeader("Expires", TEST_DATE) .withBody("Hello world!"))); - S3Client s3Client = getS3Client(wm); - Assertions.assertThatCode(() -> s3Client.headObject(r -> { r.bucket("s3_expires_test_dummy_bucket") .key("s3_expires_test_dummy_key"); @@ -66,15 +68,13 @@ public void headObjectRequestWithInvalidDate_doesNotThrowException(WireMockRunti } @Test - public void getObjectRequestWithInvalidDate_doesNotThrowException(WireMockRuntimeInfo wm) throws IOException { + public void getObjectRequestWithInvalidDate_doesNotThrowException() throws IOException { stubFor(any(anyUrl()) .willReturn(aResponse() .withHeader("Expires", TEST_DATE) .withBody("Hello world!"))); - S3Client s3Client = getS3Client(wm); - Assertions.assertThatCode(() -> s3Client.headObject(r -> { r.bucket("s3_expires_test_dummy_bucket") .key("s3_expires_test_dummy_key"); From b032c93a91d4565906dac980236b75108b8aa25b Mon Sep 17 00:00:00 2001 From: Krishnan Date: Mon, 29 Apr 2024 11:57:10 -0700 Subject: [PATCH 17/18] assertThat instead of assertTrue and logged the entire exception stack --- .../unmarshall/HeaderUnmarshaller.java | 2 +- .../s3/ExpiresHeaderDataTypeErrorTest.java | 23 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java index 9e7259cfdd64..c160d3c6bb35 100644 --- a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java +++ b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java @@ -76,7 +76,7 @@ public T unmarshall(XmlUnmarshallerContext context, List content, Sd .map(s -> stringToValue.convert(s, field)) .orElse(null); } catch (RuntimeException e) { - log.warn(() -> (String.format("Exception found while parsing response header {}", e.getMessage()))); + log.warn(() -> (String.format("Exception found while parsing response header {} ", e))); if (field.ignoreDataTypeConversionFailures()) { return null; } diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java index 508b34b0737a..457d0388a48c 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/ExpiresHeaderDataTypeErrorTest.java @@ -19,15 +19,14 @@ import static com.github.tomakehurst.wiremock.client.WireMock.any; import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; import java.io.IOException; import java.net.URI; import org.assertj.core.api.Assertions; -import org.junit.Before; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; @@ -53,17 +52,17 @@ public void headObjectRequestWithInvalidDate_doesNotThrowException() throws IOEx .withHeader("Expires", TEST_DATE) .withBody("Hello world!"))); - Assertions.assertThatCode(() -> s3Client.headObject(r -> { + assertThatCode(() -> s3Client.headObject(r -> { r.bucket("s3_expires_test_dummy_bucket") .key("s3_expires_test_dummy_key"); })) .doesNotThrowAnyException(); - assertTrue(s3Client.headObject(r -> {r.bucket("s3_expires_test_dummy_bucket") - .key("s3_expires_test_dummy_key");}).expires() == null); + assertThat(s3Client.headObject(r -> {r.bucket("s3_expires_test_dummy_bucket") + .key("s3_expires_test_dummy_key");}).expires()).isNull(); - assertEquals(s3Client.headObject(r -> {r.bucket("s3_expires_test_dummy_bucket") - .key("s3_expires_test_dummy_key");}).expiresString(), TEST_DATE); + assertThat(s3Client.headObject(r -> {r.bucket("s3_expires_test_dummy_bucket") + .key("s3_expires_test_dummy_key");}).expiresString()).isEqualTo(TEST_DATE); } @@ -81,11 +80,11 @@ public void getObjectRequestWithInvalidDate_doesNotThrowException() throws IOExc })) .doesNotThrowAnyException(); - assertTrue(s3Client.getObject(r -> {r.bucket("s3_expires_test_dummy_bucket") - .key("s3_expires_test_dummy_key");}).response().expires() == null); + assertThat(s3Client.getObject(r -> {r.bucket("s3_expires_test_dummy_bucket") + .key("s3_expires_test_dummy_key");}).response().expires()).isNull(); - assertEquals(s3Client.getObject(r -> {r.bucket("s3_expires_test_dummy_bucket") - .key("s3_expires_test_dummy_key");}).response().expiresString(), TEST_DATE); + assertThat(s3Client.getObject(r -> {r.bucket("s3_expires_test_dummy_bucket") + .key("s3_expires_test_dummy_key");}).response().expiresString()).isEqualTo(TEST_DATE); } } From b0c759aeb4dd58b5aede4744dbac1b9da80cad7f Mon Sep 17 00:00:00 2001 From: Krishnan Date: Mon, 29 Apr 2024 12:21:09 -0700 Subject: [PATCH 18/18] modified error message --- .../protocols/xml/internal/unmarshall/HeaderUnmarshaller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java index c160d3c6bb35..4f1121da3dcc 100644 --- a/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java +++ b/core/protocols/aws-xml-protocol/src/main/java/software/amazon/awssdk/protocols/xml/internal/unmarshall/HeaderUnmarshaller.java @@ -76,7 +76,7 @@ public T unmarshall(XmlUnmarshallerContext context, List content, Sd .map(s -> stringToValue.convert(s, field)) .orElse(null); } catch (RuntimeException e) { - log.warn(() -> (String.format("Exception found while parsing response header {} ", e))); + log.warn(() -> "Exception found while parsing response header " , e); if (field.ignoreDataTypeConversionFailures()) { return null; }