diff --git a/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml b/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml index 73481b3f68564..ecf8041030041 100755 --- a/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml +++ b/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml @@ -23,12 +23,12 @@ - - - - - - + + + + + + diff --git a/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml b/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml index 67e2613f1df90..3726620b1b49c 100755 --- a/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml +++ b/eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml @@ -661,10 +661,10 @@ - - - - + + + + diff --git a/sdk/core/azure-core-jackson-tests/README.md b/sdk/core/azure-core-jackson-tests/README.md index 6ea03171c25b3..c95771c8ca704 100644 --- a/sdk/core/azure-core-jackson-tests/README.md +++ b/sdk/core/azure-core-jackson-tests/README.md @@ -4,7 +4,9 @@ Azure Core Jackson Tests is a test package that tests `azure-core` library again ## Getting started -This package is intended to run in Live Test Azure Pipeline (`java - core - test`) under `jackson_versions` test name), but you can run it locally by setting `AZURE_CORE_TEST_JACKSON_VERSION` environment variable. +This package is intended to run in Live Test Azure Pipeline (`java - core - test`) under `jackson_supported_versions` and +`jackson_unsupported_versions` test name), but you can run it locally by setting `AZURE_CORE_TEST_SUPPORTED_JACKSON_VERSION` +or `AZURE_CORE_TEST_UNSUPPORTED_JACKSON_VERSION` environment variables. ## Key concepts @@ -15,14 +17,14 @@ Here's how you can test arbitrary Jackson version from `azure-core-jackson-tests Windows: ```powershell -PS> $env:AZURE_CORE_TEST_JACKSON_VERSION="2.12.1" +PS> $env:AZURE_CORE_TEST_SUPPORTED_JACKSON_VERSION="2.12.1" PS> mvn test ``` Linux: ```bash -$ export AZURE_CORE_TEST_JACKSON_VERSION="2.12.2" +$ export AZURE_CORE_TEST_SUPPORTED_JACKSON_VERSION="2.12.2" $ mvn test ``` diff --git a/sdk/core/azure-core-jackson-tests/pom.xml b/sdk/core/azure-core-jackson-tests/pom.xml index 7ac3e1aadcc17..323cc7454c5c5 100644 --- a/sdk/core/azure-core-jackson-tests/pom.xml +++ b/sdk/core/azure-core-jackson-tests/pom.xml @@ -55,6 +55,7 @@ com.azure azure-core 1.21.0-beta.1 + test @@ -159,40 +160,34 @@ - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.0.0-M3 - - - com.azure:azure-core - - - - **/RestProxyXMLTests.java - - - - - - - java-lts + default - [11,) + true + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M3 + + true + + + + + - jackson-version-test-matrix + jackson-supported-version-test-matrix - env.AZURE_CORE_TEST_JACKSON_VERSION + env.AZURE_CORE_TEST_SUPPORTED_JACKSON_VERSION - ${env.AZURE_CORE_TEST_JACKSON_VERSION} + ${env.AZURE_CORE_TEST_SUPPORTED_JACKSON_VERSION} @@ -226,8 +221,85 @@ ${jackson.version} test + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M3 + + + com.azure:azure-core + + + + **/RestProxyXMLTests.java + **/UnsupportedJacksonVersionsTests.java + + + + + + + + jackson-unsupported-version-test + + env.AZURE_CORE_TEST_UNSUPPORTED_JACKSON_VERSION + + + ${env.AZURE_CORE_TEST_UNSUPPORTED_JACKSON_VERSION} + + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + test + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + test + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + test + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + test + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson.version} + test + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M3 + + + **/UnsupportedJacksonVersionsTests.java + + + + + + diff --git a/sdk/core/azure-core-jackson-tests/src/test/java/com/azure/core/implementation/jackson/UnsupportedJacksonVersionsTests.java b/sdk/core/azure-core-jackson-tests/src/test/java/com/azure/core/implementation/jackson/UnsupportedJacksonVersionsTests.java new file mode 100644 index 0000000000000..f4ee0632b536c --- /dev/null +++ b/sdk/core/azure-core-jackson-tests/src/test/java/com/azure/core/implementation/jackson/UnsupportedJacksonVersionsTests.java @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.implementation.jackson; + +import com.azure.core.util.CoreUtils; +import com.azure.core.util.serializer.JacksonAdapter; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class UnsupportedJacksonVersionsTests { + @Test + public void testUnsupportedVersion() { + String jacksonVersionString = ObjectMapper.class.getPackage().getImplementationVersion(); + String azureCoreVersion = CoreUtils + .getProperties("azure-core.properties") + .getOrDefault("version", null); + + JacksonVersion version = JacksonVersion.getInstance(); + String helpInfo = version.getHelpInfo(); + assertTrue(helpInfo.contains("jackson-annotations=" + jacksonVersionString)); + assertTrue(helpInfo.contains("jackson-core=" + jacksonVersionString)); + assertTrue(helpInfo.contains("jackson-databind=" + jacksonVersionString)); + assertTrue(helpInfo.contains("jackson-dataformat-xml=" + jacksonVersionString)); + assertTrue(helpInfo.contains("jackson-datatype-jsr310=" + jacksonVersionString)); + assertTrue(helpInfo.contains("azure-core=" + azureCoreVersion)); + + Error error = assertThrows(Error.class, () -> new JacksonAdapter()); + assertTrue(error.getMessage().contains(helpInfo)); + } +} diff --git a/sdk/core/azure-core/pom.xml b/sdk/core/azure-core/pom.xml index 22c75d3a1bcf5..6dcf854e48651 100644 --- a/sdk/core/azure-core/pom.xml +++ b/sdk/core/azure-core/pom.xml @@ -269,6 +269,7 @@ --add-opens com.azure.core/com.azure.core.implementation.models.jsonflatten=com.fasterxml.jackson.databind --add-opens com.azure.core/com.azure.core.implementation.models.jsonflatten=ALL-UNNAMED --add-opens com.azure.core/com.azure.core.implementation.serializer=ALL-UNNAMED + --add-opens com.azure.core/com.azure.core.implementation.jackson=ALL-UNNAMED --add-opens com.azure.core/com.azure.core.models=ALL-UNNAMED --add-opens com.azure.core/com.azure.core.util=ALL-UNNAMED --add-opens com.azure.core/com.azure.core.util.logging=ALL-UNNAMED diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java index e18917da93c62..6a1f9c9812060 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java @@ -11,13 +11,12 @@ import com.azure.core.http.HttpRequest; import com.azure.core.http.HttpResponse; import com.azure.core.implementation.http.HttpPipelineCallContextHelper; +import com.azure.core.implementation.jackson.ObjectMapperShim; import com.azure.core.util.Context; import com.azure.core.util.CoreUtils; import com.azure.core.util.UrlBuilder; import com.azure.core.util.logging.ClientLogger; import com.azure.core.util.logging.LogLevel; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; import reactor.core.publisher.Mono; import java.io.ByteArrayOutputStream; @@ -37,7 +36,7 @@ * The pipeline policy that handles logging of HTTP requests and responses. */ public class HttpLoggingPolicy implements HttpPipelinePolicy { - private static final ObjectMapper PRETTY_PRINTER = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); + private static final ObjectMapperShim PRETTY_PRINTER = ObjectMapperShim.createPrettyPrintMapper(); private static final int MAX_BODY_LOG_SIZE = 1024 * 16; private static final String REDACTED_PLACEHOLDER = "REDACTED"; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/SemanticVersion.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/SemanticVersion.java new file mode 100644 index 0000000000000..30cb2dbbe5f15 --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/SemanticVersion.java @@ -0,0 +1,257 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.implementation; + +import com.azure.core.util.CoreUtils; +import java.io.IOException; +import java.util.Objects; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +/** + * Implements lightweight semantic version based on https://semver.org/ for internal use. + */ +public final class SemanticVersion implements Comparable { + + /** + * Represents unknown version - either invalid or missing. + */ + public static final String UNKNOWN_VERSION = "unknown"; + + private final int major; + private final int minor; + private final int patch; + private final String prerelease; + private final String versionString; + + /** + * Returns implementation version of the package for given class. If version can't be retrieved or parsed, returns invalid version. + * + * @param className - class name to get package version of. + * @return parsed {@link SemanticVersion} or invalid one. + */ + public static SemanticVersion getPackageVersionForClass(String className) { + try { + return getPackageVersion(Class.forName(className)); + } catch (Throwable e) { + return SemanticVersion.createInvalid(); + } + } + + /** + * Parses semver 2.0.0 string. If version can't be retrieved or parsed, returns invalid version. + * + * @param version to parse. + * @return parsed {@link SemanticVersion} or invalid one. + */ + public static SemanticVersion parse(String version) { + Objects.requireNonNull(version, "'version' cannot be null."); + String[] parts = version.split("\\."); + if (parts.length < 3) { + return createInvalid(version); + } + + int majorDotIdx = version.indexOf('.'); + int minorDotIdx = version.indexOf('.', majorDotIdx + 1); + if (majorDotIdx < 0 || minorDotIdx < 0) { + return createInvalid(version); + } + + int patchEndIdx = minorDotIdx + 1; + while (patchEndIdx < version.length()) { + Character ch = version.charAt(patchEndIdx); + + // accommodate common broken semantic versions (e.g. 1.2.3.4) + if (ch == '.' || ch == '-' || ch == '+') { + break; + } + + patchEndIdx++; + } + + int extEndIdx = version.indexOf('+', patchEndIdx); + if (extEndIdx < 0) { + extEndIdx = version.length(); + } + + try { + Integer major = Integer.valueOf(version.substring(0, majorDotIdx)); + Integer minor = Integer.valueOf(version.substring(majorDotIdx + 1, minorDotIdx)); + Integer patch = Integer.valueOf(version.substring(minorDotIdx + 1, patchEndIdx)); + + String prerelease = (patchEndIdx == extEndIdx) ? "" : version.substring(patchEndIdx + 1, extEndIdx); + return new SemanticVersion(major, minor, patch, prerelease, version); + } catch (Throwable ex) { + return createInvalid(version); + } + } + + /** + * Returns implementation version of the package for given class. + * + * @param clazz - class to get package version of. + * @return parsed {@link SemanticVersion} or invalid one. + */ + private static SemanticVersion getPackageVersion(Class clazz) { + Objects.requireNonNull(clazz, "'clazz' cannot be null."); + if (clazz.getPackage() == null) { + return createInvalid(); + } + + String versionStr = clazz.getPackage().getImplementationVersion(); + + if (versionStr != null) { + return parse(versionStr); + } + + // if versionStr is null, try loading the version from the manifest in the jar file + JarFile jar = null; + try { + jar = new JarFile(clazz.getProtectionDomain().getCodeSource().getLocation().getFile()); + Manifest manifest = jar.getManifest(); + versionStr = manifest.getMainAttributes().getValue("Implementation-Version"); + if (versionStr == null) { + versionStr = manifest.getMainAttributes().getValue("Bundle-Version"); + } + return parse(versionStr); + } catch (Throwable t) { + return createInvalid(); + } finally { + if (jar != null) { + try { + jar.close(); + } catch (IOException e) { + // ignored + } + } + } + } + + /** + * Creates invalid semantic version. + * @return instance of invalid semantic version. + */ + public static SemanticVersion createInvalid() { + return createInvalid(UNKNOWN_VERSION); + } + + /** + * Creates invalid semantic version. + */ + private static SemanticVersion createInvalid(String version) { + return new SemanticVersion(-1, -1, -1, null, version); + } + + /** + * Creates semantic version. + * + * @param major major version. + * @param minor minor version. + * @param patch patch version. + * @param prerelease extensions (including '-' or '+' separator after patch). + * @param versionString full version string. + */ + SemanticVersion(int major, int minor, int patch, String prerelease, String versionString) { + Objects.requireNonNull(versionString, "'versionString' cannot be null."); + this.major = major; + this.minor = minor; + this.patch = patch; + this.prerelease = prerelease; + this.versionString = versionString; + } + + /** + * Returns full version string that was used to create this {@code SemanticVersion} + * + * @return original version string. + */ + public String getVersionString() { + return versionString; + } + + /** + * Returns major version component or -1 for invalid version. + * + * @return major version. + */ + public int getMajorVersion() { + return major; + } + + /** + * {@inheritDoc} + */ + @Override + public int compareTo(SemanticVersion other) { + if (this == other) { + return 0; + } + + if (other == null) { + return -1; + } + + if (major != other.major) { + return major > other.major ? 1 : -1; + } + + if (minor != other.minor) { + return minor > other.minor ? 1 : -1; + } + + if (patch != other.patch) { + return patch > other.patch ? 1 : -1; + } + + if (CoreUtils.isNullOrEmpty(prerelease)) { + return CoreUtils.isNullOrEmpty(other.prerelease) ? 0 : 1; + } + + if (CoreUtils.isNullOrEmpty(other.prerelease)) { + return -1; + } + + return prerelease.compareTo(other.prerelease); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof SemanticVersion)) { + return false; + } + + SemanticVersion otherVer = (SemanticVersion) other; + + return versionString.equals(otherVer.versionString); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return versionString.hashCode(); + } + + @Override + public String toString() { + return versionString; + } + + /** + * Returns flag indicating if version is valid. + * + * @return true if version is valid, false otherwise. + */ + public boolean isValid() { + return this.major >= 0; + } +} diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/AdditionalPropertiesDeserializer.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/AdditionalPropertiesDeserializer.java similarity index 99% rename from sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/AdditionalPropertiesDeserializer.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/AdditionalPropertiesDeserializer.java index b48fc265040ab..028091c91df5a 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/AdditionalPropertiesDeserializer.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/AdditionalPropertiesDeserializer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.annotation.JsonFlatten; import com.azure.core.implementation.TypeUtil; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/AdditionalPropertiesSerializer.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/AdditionalPropertiesSerializer.java similarity index 99% rename from sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/AdditionalPropertiesSerializer.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/AdditionalPropertiesSerializer.java index 942b28cf89961..356e2b99ce49c 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/AdditionalPropertiesSerializer.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/AdditionalPropertiesSerializer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.implementation.TypeUtil; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/Base64UrlSerializer.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/Base64UrlSerializer.java similarity index 95% rename from sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/Base64UrlSerializer.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/Base64UrlSerializer.java index fbdfd842a32d2..e6a4b4d8f4374 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/Base64UrlSerializer.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/Base64UrlSerializer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.util.Base64Url; import com.fasterxml.jackson.core.JsonGenerator; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/ByteArraySerializer.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ByteArraySerializer.java similarity index 96% rename from sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/ByteArraySerializer.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ByteArraySerializer.java index 9d821c0a5cc99..794e16a94b4ca 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/ByteArraySerializer.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ByteArraySerializer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/DateTimeDeserializer.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/DateTimeDeserializer.java similarity index 97% rename from sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/DateTimeDeserializer.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/DateTimeDeserializer.java index b7570318675be..5bda3008e656c 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/DateTimeDeserializer.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/DateTimeDeserializer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/DateTimeRfc1123Serializer.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/DateTimeRfc1123Serializer.java similarity index 96% rename from sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/DateTimeRfc1123Serializer.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/DateTimeRfc1123Serializer.java index 78a451e9c2955..a09370236b90e 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/DateTimeRfc1123Serializer.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/DateTimeRfc1123Serializer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.util.DateTimeRfc1123; import com.fasterxml.jackson.core.JsonGenerator; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/DateTimeSerializer.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/DateTimeSerializer.java similarity index 97% rename from sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/DateTimeSerializer.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/DateTimeSerializer.java index 6a5851f089c6d..ae6db32f5213a 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/DateTimeSerializer.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/DateTimeSerializer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/DurationSerializer.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/DurationSerializer.java similarity index 98% rename from sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/DurationSerializer.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/DurationSerializer.java index faa6448430bc0..0ea148a8dd79a 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/DurationSerializer.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/DurationSerializer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/FlatteningDeserializer.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/FlatteningDeserializer.java similarity index 99% rename from sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/FlatteningDeserializer.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/FlatteningDeserializer.java index a2ea39707baf1..dd60f00072659 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/FlatteningDeserializer.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/FlatteningDeserializer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.annotation.JsonFlatten; import com.azure.core.implementation.TypeUtil; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/FlatteningSerializer.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/FlatteningSerializer.java similarity index 99% rename from sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/FlatteningSerializer.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/FlatteningSerializer.java index 80ae70a51bb3a..1a667902da729 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/FlatteningSerializer.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/FlatteningSerializer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.annotation.JsonFlatten; import com.azure.core.util.ExpandableStringEnum; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/GeoJsonDeserializer.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/GeoJsonDeserializer.java similarity index 99% rename from sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/GeoJsonDeserializer.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/GeoJsonDeserializer.java index d4ea7a3fa50cc..fbcb98bdb81b1 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/GeoJsonDeserializer.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/GeoJsonDeserializer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.models.GeoBoundingBox; import com.azure.core.models.GeoCollection; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/GeoJsonSerializer.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/GeoJsonSerializer.java similarity index 99% rename from sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/GeoJsonSerializer.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/GeoJsonSerializer.java index 207b8aa2907a7..cf1072ecd4e98 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/GeoJsonSerializer.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/GeoJsonSerializer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.implementation.GeoObjectHelper; import com.azure.core.models.GeoBoundingBox; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/HeaderCollectionHandler.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/HeaderCollectionHandler.java new file mode 100644 index 0000000000000..a6a2cfa6a3a3c --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/HeaderCollectionHandler.java @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.implementation.jackson; + +import com.azure.core.implementation.ReflectionUtils; +import com.azure.core.util.logging.ClientLogger; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/* + * Internal helper class that helps manage converting headers into their header collection. + */ +final class HeaderCollectionHandler { + private static final int CACHE_SIZE_LIMIT = 10000; + private static final Map FIELD_TO_SETTER_CACHE = new ConcurrentHashMap<>(); + private final String prefix; + private final int prefixLength; + private final Map values; + private final Field declaringField; + + HeaderCollectionHandler(String prefix, Field declaringField) { + this.prefix = prefix; + this.prefixLength = prefix.length(); + this.values = new HashMap<>(); + this.declaringField = declaringField; + } + + boolean headerStartsWithPrefix(String headerName) { + return headerName.startsWith(prefix); + } + + void addHeader(String headerName, String headerValue) { + values.put(headerName.substring(prefixLength), headerValue); + } + + @SuppressWarnings("deprecation") + void injectValuesIntoDeclaringField(Object deserializedHeaders, ClientLogger logger) { + /* + * First check if the deserialized headers type has a public setter. + */ + if (usePublicSetter(deserializedHeaders, logger)) { + return; + } + + /* + * Otherwise, fallback to setting the field directly. + */ + final boolean declaredFieldAccessibleBackup = declaringField.isAccessible(); + try { + if (!declaredFieldAccessibleBackup) { + AccessController.doPrivileged((PrivilegedAction) () -> { + declaringField.setAccessible(true); + return null; + }); + } + declaringField.set(deserializedHeaders, values); + logger.verbose("Set header collection by accessing the field directly."); + } catch (IllegalAccessException ex) { + logger.warning("Failed to inject header collection values into deserialized headers.", ex); + } finally { + if (!declaredFieldAccessibleBackup) { + AccessController.doPrivileged((PrivilegedAction) () -> { + declaringField.setAccessible(false); + return null; + }); + } + } + } + + private boolean usePublicSetter(Object deserializedHeaders, ClientLogger logger) { + final Class clazz = deserializedHeaders.getClass(); + final String clazzSimpleName = clazz.getSimpleName(); + final String fieldName = declaringField.getName(); + + MethodHandle setterHandler = getFromCache(declaringField, field -> { + MethodHandles.Lookup lookupToUse; + try { + lookupToUse = ReflectionUtils.getLookupToUse(clazz); + } catch (Throwable t) { + logger.verbose("Failed to retrieve MethodHandles.Lookup for field {}.", field, t); + return null; + } + + String setterName = getPotentialSetterName(fieldName); + + try { + MethodHandle handle = lookupToUse.findVirtual(clazz, setterName, + MethodType.methodType(clazz, Map.class)); + + logger.verbose("Using MethodHandle for setter {} on class {}.", setterName, clazzSimpleName); + + return handle; + } catch (ReflectiveOperationException ex) { + logger.verbose("Failed to retrieve MethodHandle for setter {} on class {}.", setterName, + clazzSimpleName, ex); + } + + try { + Method setterMethod = deserializedHeaders.getClass() + .getDeclaredMethod(setterName, Map.class); + MethodHandle handle = lookupToUse.unreflect(setterMethod); + + logger.verbose("Using unreflected MethodHandle for setter {} on class {}.", setterName, + clazzSimpleName); + + return handle; + } catch (ReflectiveOperationException ex) { + logger.verbose("Failed to unreflect MethodHandle for setter {} on class {}.", setterName, + clazzSimpleName, ex); + } + + return null; + }); + + if (setterHandler == null) { + return false; + } + + try { + setterHandler.invokeWithArguments(deserializedHeaders, values); + logger.verbose("Set header collection {} on class {} using MethodHandle.", fieldName, clazzSimpleName); + + return true; + } catch (Throwable ex) { + logger.verbose("Failed to set header {} collection on class {} using MethodHandle.", fieldName, + clazzSimpleName, ex); + return false; + } + } + + private static String getPotentialSetterName(String fieldName) { + return "set" + fieldName.substring(0, 1).toUpperCase(Locale.ROOT) + fieldName.substring(1); + } + + private static MethodHandle getFromCache(Field key, Function compute) { + if (FIELD_TO_SETTER_CACHE.size() >= CACHE_SIZE_LIMIT) { + FIELD_TO_SETTER_CACHE.clear(); + } + + return FIELD_TO_SETTER_CACHE.computeIfAbsent(key, compute); + } +} diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/HttpHeadersSerializer.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/HttpHeadersSerializer.java similarity index 95% rename from sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/HttpHeadersSerializer.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/HttpHeadersSerializer.java index 1cd20f47e46fa..d0e6052b1a7f5 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/HttpHeadersSerializer.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/HttpHeadersSerializer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.http.HttpHeaders; import com.fasterxml.jackson.core.JsonGenerator; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/JacksonVersion.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/JacksonVersion.java new file mode 100644 index 0000000000000..43f9d0f50d848 --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/JacksonVersion.java @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.implementation.jackson; + +import com.azure.core.implementation.SemanticVersion; +import com.azure.core.util.CoreUtils; +import com.azure.core.util.logging.ClientLogger; + +/** + * Provides information about Jackson package versions used, detects and logs errors. + */ +final class JacksonVersion { + private SemanticVersion annotationsVersion; + private SemanticVersion coreVersion; + private SemanticVersion databindVersion; + private SemanticVersion xmlVersion; + private SemanticVersion jsr310Version; + + private static final String ANNOTATIONS_PACKAGE_NAME = "jackson-annotations"; + private static final String CORE_PACKAGE_NAME = "jackson-core"; + private static final String DATABIND_PACKAGE_NAME = "jackson-databind"; + private static final String XML_PACKAGE_NAME = "jackson-dataformat-xml"; + private static final String JSR310_PACKAGE_NAME = "jackson-datatype-jsr310"; + + private static final SemanticVersion MIN_SUPPORTED_VERSION = SemanticVersion.parse("2.10.0"); + private static final SemanticVersion MAX_SUPPORTED_VERSION = SemanticVersion.parse("2.12.4"); + + private static final String AZURE_CORE_PROPERTIES_NAME = "azure-core.properties"; + private static final String AZURE_CORE_PROPERTIES_VERSION_KEY = "version"; + + private static final String AZURE_CORE_VERSION = CoreUtils + .getProperties(AZURE_CORE_PROPERTIES_NAME) + .getOrDefault(AZURE_CORE_PROPERTIES_VERSION_KEY, SemanticVersion.UNKNOWN_VERSION); + + private static JacksonVersion instance = null; + + private final String helpString; + private final ClientLogger logger = new ClientLogger(JacksonVersion.class); + + private JacksonVersion() { + annotationsVersion = SemanticVersion.getPackageVersionForClass("com.fasterxml.jackson.annotation.JsonProperty"); + coreVersion = SemanticVersion.getPackageVersionForClass("com.fasterxml.jackson.core.JsonGenerator"); + databindVersion = SemanticVersion.getPackageVersionForClass("com.fasterxml.jackson.databind.ObjectMapper"); + xmlVersion = SemanticVersion.getPackageVersionForClass("com.fasterxml.jackson.dataformat.xml.XmlMapper"); + jsr310Version = SemanticVersion.getPackageVersionForClass("com.fasterxml.jackson.datatype.jsr310.JavaTimeModule"); + checkVersion(annotationsVersion, ANNOTATIONS_PACKAGE_NAME); + checkVersion(coreVersion, CORE_PACKAGE_NAME); + checkVersion(databindVersion, DATABIND_PACKAGE_NAME); + checkVersion(xmlVersion, XML_PACKAGE_NAME); + checkVersion(jsr310Version, JSR310_PACKAGE_NAME); + helpString = formatHelpString(); + logger.info(helpString); + } + + /** + * Returns help info containing actual detected package versions. + * + * @return diagnostics information with detected versions. + */ + public String getHelpInfo() { + return helpString; + } + + /** + * Gets {@code JacksonVersion} instance singleton. + */ + public static synchronized JacksonVersion getInstance() { + if (instance == null) { + instance = new JacksonVersion(); + } + + return instance; + } + + /** + * Checks package version and logs if any issues detected. + */ + private void checkVersion(SemanticVersion version, String packageName) { + if (!version.isValid()) { + logger.warning("Could not find version of '{}'.", packageName); + } + + if (version.compareTo(MIN_SUPPORTED_VERSION) < 0) { + logger.error("Version '{}' of package '{}' is not supported (older than earliest supported version - `{}`), please upgrade.", version.getVersionString(), packageName, MIN_SUPPORTED_VERSION); + } + + if (version.getMajorVersion() > MAX_SUPPORTED_VERSION.getMajorVersion()) { + logger.error("Major version '{}' of package '{}' is newer than latest supported version - '{}'.", + version.getVersionString(), + packageName, + MAX_SUPPORTED_VERSION.getVersionString()); + } + } + + /** + * Generates help information with versions detected in runtime. + */ + private String formatHelpString() { + // TODO(limolkova): add link to troubleshooting docs + return new StringBuilder() + .append("Package versions: ") + .append(ANNOTATIONS_PACKAGE_NAME) + .append("=") + .append(annotationsVersion.getVersionString()) + .append(", ") + .append(CORE_PACKAGE_NAME) + .append("=") + .append(coreVersion.getVersionString()) + .append(", ") + .append(DATABIND_PACKAGE_NAME) + .append("=") + .append(databindVersion.getVersionString()) + .append(", ") + .append(XML_PACKAGE_NAME) + .append("=") + .append(xmlVersion.getVersionString()) + .append(", ") + .append(JSR310_PACKAGE_NAME) + .append("=") + .append(jsr310Version.getVersionString()) + .append(", ") + .append("azure-core=") + .append(AZURE_CORE_VERSION) + .toString(); + } +} diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ObjectMapperFactory.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ObjectMapperFactory.java new file mode 100644 index 0000000000000..68d69ceb2a102 --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ObjectMapperFactory.java @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.implementation.jackson; + +import com.azure.core.util.logging.ClientLogger; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.cfg.MapperBuilder; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser; +import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +/** + * Constructs and configures {@link ObjectMapper} instances. + */ +final class ObjectMapperFactory { + private final ClientLogger logger = new ClientLogger(ObjectMapperFactory.class); + + private static final String MUTABLE_COERCION_CONFIG = "com.fasterxml.jackson.databind.cfg.MutableCoercionConfig"; + private static final String COERCION_INPUT_SHAPE = "com.fasterxml.jackson.databind.cfg.CoercionInputShape"; + private static final String COERCION_ACTION = "com.fasterxml.jackson.databind.cfg.CoercionAction"; + + private MethodHandle coersionConfigDefaults; + private MethodHandle setCoercion; + private Object coercionInputShapeEmptyString; + private Object coercionActionAsNull; + private boolean useReflectionToSetCoercion; + + private static ObjectMapperFactory instance; + + public static final ObjectMapperFactory INSTANCE = new ObjectMapperFactory(); + + private ObjectMapperFactory() { + MethodHandles.Lookup publicLookup = MethodHandles.publicLookup(); + + try { + Class mutableCoercionConfig = Class.forName(MUTABLE_COERCION_CONFIG); + Class coercionInputShapeClass = Class.forName(COERCION_INPUT_SHAPE); + Class coercionActionClass = Class.forName(COERCION_ACTION); + + coersionConfigDefaults = publicLookup.findVirtual(ObjectMapper.class, "coercionConfigDefaults", + MethodType.methodType(mutableCoercionConfig)); + setCoercion = publicLookup.findVirtual(mutableCoercionConfig, "setCoercion", + MethodType.methodType(mutableCoercionConfig, coercionInputShapeClass, coercionActionClass)); + coercionInputShapeEmptyString = publicLookup.findStaticGetter(coercionInputShapeClass, "EmptyString", + coercionInputShapeClass).invoke(); + coercionActionAsNull = publicLookup.findStaticGetter(coercionActionClass, "AsNull", coercionActionClass) + .invoke(); + useReflectionToSetCoercion = true; + } catch (Throwable ex) { + logger.verbose("Failed to retrieve MethodHandles used to set coercion configurations. " + + "Setting coercion configurations will be skipped.", ex); + } + } + + public ObjectMapper createJsonMapper(ObjectMapperShim innerMapperShim) { + ObjectMapper innerMapper = innerMapperShim.getMapper(); + ObjectMapper flatteningMapper = initializeMapperBuilder(JsonMapper.builder()) + .addModule(FlatteningSerializer.getModule(innerMapper)) + .addModule(FlatteningDeserializer.getModule(innerMapper)) + .build(); + + return initializeMapperBuilder(JsonMapper.builder()) + // Order matters: must register in reverse order of hierarchy + .addModule(AdditionalPropertiesSerializer.getModule(flatteningMapper)) + .addModule(AdditionalPropertiesDeserializer.getModule(flatteningMapper)) + .addModule(FlatteningSerializer.getModule(innerMapper)) + .addModule(FlatteningDeserializer.getModule(innerMapper)) + .build(); + } + + public ObjectMapper createXmlMapper() { + ObjectMapper xmlMapper = initializeMapperBuilder(XmlMapper.builder()) + .defaultUseWrapper(false) + .enable(ToXmlGenerator.Feature.WRITE_XML_DECLARATION) + /* + * In Jackson 2.12 the default value of this feature changed from true to false. + * https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.12#xml-module + */ + .enable(FromXmlParser.Feature.EMPTY_ELEMENT_AS_NULL) + .build(); + + + if (useReflectionToSetCoercion) { + try { + Object object = coersionConfigDefaults.invoke(xmlMapper); + setCoercion.invoke(object, coercionInputShapeEmptyString, coercionActionAsNull); + } catch (Throwable e) { + logger.verbose("Failed to set coercion actions.", e); + } + } else { + logger.verbose("Didn't set coercion defaults as it wasn't found on the classpath."); + } + + return xmlMapper; + } + + public ObjectMapper createSimpleMapper() { + return initializeMapperBuilder(JsonMapper.builder()).build(); + } + + public ObjectMapper createDefaultMapper() { + return new ObjectMapper(); + } + + public ObjectMapper createPrettyPrintMapper() { + return new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); + } + + public ObjectMapper createHeaderMapper() { + return initializeMapperBuilder(JsonMapper.builder()) + .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + .build(); + } + + @SuppressWarnings("deprecation") + private > S initializeMapperBuilder(S mapper) { + mapper.enable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS) + .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT) + .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .serializationInclusion(JsonInclude.Include.NON_NULL) + .addModule(new JavaTimeModule()) + .addModule(ByteArraySerializer.getModule()) + .addModule(Base64UrlSerializer.getModule()) + .addModule(DateTimeSerializer.getModule()) + .addModule(DateTimeDeserializer.getModule()) + .addModule(DateTimeRfc1123Serializer.getModule()) + .addModule(DurationSerializer.getModule()) + .addModule(HttpHeadersSerializer.getModule()) + .addModule(UnixTimeSerializer.getModule()) + .addModule(UnixTimeDeserializer.getModule()) + .addModule(GeoJsonSerializer.getModule()) + .addModule(GeoJsonDeserializer.getModule()) + .visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) + .visibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE) + .visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE) + .visibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.NONE); + + return mapper; + } +} diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ObjectMapperShim.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ObjectMapperShim.java new file mode 100644 index 0000000000000..a4da8e40e5bca --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ObjectMapperShim.java @@ -0,0 +1,379 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.implementation.jackson; + +import com.azure.core.annotation.HeaderCollection; +import com.azure.core.http.HttpHeader; +import com.azure.core.http.HttpHeaders; +import com.azure.core.implementation.TypeUtil; +import com.azure.core.util.logging.ClientLogger; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * Wraps {@link ObjectMapper} creation and proxies calls and provides diagnostics info in case + * of potential version mismatch issues that manifest with {@link LinkageError}. + * + */ +public final class ObjectMapperShim { + private static final JacksonVersion JACKSON_VERSION = JacksonVersion.getInstance(); + private static final ClientLogger LOGGER = new ClientLogger(ObjectMapperShim.class); + + // don't add static fields that might cause Jackson classes to initialize + private static final int CACHE_SIZE_LIMIT = 10000; + + private static final Map TYPE_TO_JAVA_TYPE_CACHE = new ConcurrentHashMap<>(); + + /** + * Creates and configures JSON {@code ObjectMapper} capable of serializing azure.core types, with flattening and additional properties support. + * + * @param innerMapperShim inner mapper to use for non-azure specific serialization. + * @return Instance of shimmed {@code ObjectMapperShim}. + */ + public static ObjectMapperShim createJsonMapper(ObjectMapperShim innerMapperShim) { + try { + ObjectMapper mapper = ObjectMapperFactory.INSTANCE.createJsonMapper(innerMapperShim); + return new ObjectMapperShim(mapper); + } catch (LinkageError ex) { + throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex)); + } + } + + /** + * Creates and configures XML {@code ObjectMapper} capable of serializing azure.core types. + * + * @return Instance of shimmed {@code ObjectMapperShim}. + */ + public static ObjectMapperShim createXmlMapper() { + try { + ObjectMapper mapper = ObjectMapperFactory.INSTANCE.createXmlMapper(); + return new ObjectMapperShim(mapper); + } catch (LinkageError ex) { + throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex)); + } + } + + /** + * Creates and configures JSON {@code ObjectMapper}. + * + * @return Instance of shimmed {@code ObjectMapperShim}. + */ + public static ObjectMapperShim createSimpleMapper() { + try { + ObjectMapper mapper = ObjectMapperFactory.INSTANCE.createSimpleMapper(); + return new ObjectMapperShim(mapper); + } catch (LinkageError ex) { + throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex)); + } + } + + /** + * Creates JSON {@code ObjectMapper} with default Jackson settings. + * + * @return Instance of shimmed {@code ObjectMapperShim}. + */ + public static ObjectMapperShim createDefaultMapper() { + try { + ObjectMapper mapper = ObjectMapperFactory.INSTANCE.createDefaultMapper(); + return new ObjectMapperShim(mapper); + } catch (LinkageError ex) { + throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex)); + } + } + + /** + * Creates JSON {@code ObjectMapper} with default Jackson settings, but capable of pretty-printing. + * + * @return Instance of shimmed {@code ObjectMapperShim}. + */ + public static ObjectMapperShim createPrettyPrintMapper() { + try { + ObjectMapper mapper = ObjectMapperFactory.INSTANCE.createPrettyPrintMapper(); + return new ObjectMapperShim(mapper); + } catch (LinkageError ex) { + throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex)); + } + } + + /** + * Creates and configures JSON {@code ObjectMapper} for headers serialization. + * + * @return Instance of shimmed {@code ObjectMapperShim}. + */ + public static ObjectMapperShim createHeaderMapper() { + try { + ObjectMapper mapper = ObjectMapperFactory.INSTANCE.createHeaderMapper(); + return new ObjectMapperShim(mapper); + } catch (LinkageError ex) { + throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex)); + } + } + + private final ObjectMapper mapper; + + private ObjectMapperShim(ObjectMapper mapper) { + this.mapper = mapper; + } + + /** + * Gets wrapped {@code ObjectMapper} instance. Use with caution. + * @return + */ + public ObjectMapper getMapper() { + return this.mapper; + } + + /** + * Serializes Java object as a string. + * + * @param value object to serialize. + * @return Serialized string. + * @throws IOException + */ + public String writeValueAsString(Object value) throws IOException { + try { + return mapper.writeValueAsString(value); + } catch (LinkageError ex) { + throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex)); + } + } + + /** + * Serializes Java object as a byte array. + * + * @param value object to serialize. + * @return Serialized byte array. + * @throws IOException + */ + public byte[] writeValueAsBytes(Object value) throws IOException { + try { + return mapper.writeValueAsBytes(value); + } catch (LinkageError ex) { + throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex)); + } + } + + /** + * Serializes Java object and write it to stream. + * + * @param out stream to write serialized object to. + * @param value object to serialize. + * @throws IOException + */ + public void writeValue(OutputStream out, Object value) throws IOException { + try { + mapper.writeValue(out, value); + } catch (LinkageError ex) { + throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex)); + } + } + + /** + * Deserializes Java object from a string. + * + * @param content serialized object. + * @param valueType type of the value. + * @return Deserialized object. + * @throws IOException + */ + public T readValue(String content, final Type valueType) throws IOException { + try { + final JavaType javaType = createJavaType(valueType); + return mapper.readValue(content, javaType); + } catch (LinkageError ex) { + throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex)); + } + } + + /** + * Deserializes Java object from a byte array. + * + * @param src serialized object. + * @param valueType type of the value. + * @return Deserialized object. + * @throws IOException + */ + public T readValue(byte[] src, final Type valueType) throws IOException { + try { + final JavaType javaType = createJavaType(valueType); + return mapper.readValue(src, javaType); + } catch (LinkageError ex) { + throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex)); + } + } + + /** + * Reads and deserializes Java object from a stream. + * + * @param src serialized object. + * @param valueType type of the value. + * @return Deserialized object. + * @throws IOException + */ + public T readValue(InputStream src, final Type valueType) throws IOException { + try { + final JavaType javaType = createJavaType(valueType); + return mapper.readValue(src, javaType); + } catch (LinkageError ex) { + throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex)); + } + } + + /** + * Reads JSON tree from string. + * @param content serialized JSON tree. + * @return {@code JsonNode} instance + * @throws IOException + */ + public JsonNode readTree(String content) throws IOException { + try { + return mapper.readTree(content); + } catch (LinkageError ex) { + throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex)); + } + } + + /** + * Reads JSON tree from byte array. + * @param content serialized JSON tree. + * @return {@code JsonNode} instance + * @throws IOException + */ + public JsonNode readTree(byte[] content) throws IOException { + try { + return mapper.readTree(content); + } catch (LinkageError ex) { + throw LOGGER.logThrowableAsError(new LinkageError(JACKSON_VERSION.getHelpInfo(), ex)); + } + } + + private JavaType createJavaType(Type type) { + if (type == null) { + return null; + } else if (type instanceof JavaType) { + return (JavaType) type; + } else if (type instanceof ParameterizedType) { + final ParameterizedType parameterizedType = (ParameterizedType) type; + final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + JavaType[] javaTypeArguments = new JavaType[actualTypeArguments.length]; + for (int i = 0; i != actualTypeArguments.length; i++) { + javaTypeArguments[i] = createJavaType(actualTypeArguments[i]); + } + + return getFromCache(type, t -> mapper.getTypeFactory() + .constructParametricType((Class) parameterizedType.getRawType(), javaTypeArguments)); + } else { + return getFromCache(type, t -> mapper.getTypeFactory().constructType(t)); + } + } + + public T deserialize(HttpHeaders headers, Type deserializedHeadersType) throws IOException { + if (deserializedHeadersType == null) { + return null; + } + + T deserializedHeaders = mapper.convertValue(headers, createJavaType(deserializedHeadersType)); + + final Class deserializedHeadersClass = TypeUtil.getRawClass(deserializedHeadersType); + final Field[] declaredFields = deserializedHeadersClass.getDeclaredFields(); + + /* + * A list containing all handlers for header collections of the header type. + */ + final List headerCollectionHandlers = new ArrayList<>(); + + /* + * This set is an optimization where we track the first character of all HeaderCollections defined on the + * deserialized headers type. This allows us to optimize away startWiths checks which are much more costly than + * getting the first character. + */ + final Set headerCollectionsFirstCharacters = new HashSet<>(); + + /* + * Begin by looping over all declared fields and initializing all header collection information. + */ + for (final Field declaredField : declaredFields) { + if (!declaredField.isAnnotationPresent(HeaderCollection.class)) { + continue; + } + + final Type declaredFieldType = declaredField.getGenericType(); + if (!TypeUtil.isTypeOrSubTypeOf(declaredField.getType(), Map.class)) { + continue; + } + + final Type[] mapTypeArguments = TypeUtil.getTypeArguments(declaredFieldType); + if (mapTypeArguments.length != 2 + || mapTypeArguments[0] != String.class + || mapTypeArguments[1] != String.class) { + continue; + } + + final HeaderCollection headerCollectionAnnotation = declaredField.getAnnotation(HeaderCollection.class); + final String headerCollectionPrefix = headerCollectionAnnotation.value().toLowerCase(Locale.ROOT); + final int headerCollectionPrefixLength = headerCollectionPrefix.length(); + if (headerCollectionPrefixLength == 0) { + continue; + } + + headerCollectionHandlers.add(new HeaderCollectionHandler(headerCollectionPrefix, declaredField)); + headerCollectionsFirstCharacters.add(headerCollectionPrefix.charAt(0)); + } + + /* + * Then loop over all headers and check if they begin with any of the prefixes found. + */ + for (final HttpHeader header : headers) { + String headerNameLower = header.getName().toLowerCase(Locale.ROOT); + + /* + * Optimization to skip this header as it doesn't begin with any character starting header collections in + * the deserialized headers type. + */ + if (!headerCollectionsFirstCharacters.contains(headerNameLower.charAt(0))) { + continue; + } + + for (HeaderCollectionHandler headerCollectionHandler : headerCollectionHandlers) { + if (headerCollectionHandler.headerStartsWithPrefix(headerNameLower)) { + headerCollectionHandler.addHeader(header.getName(), header.getValue()); + } + } + } + + /* + * Finally, inject all found header collection values into the deserialized headers. + */ + headerCollectionHandlers.forEach(h -> h.injectValuesIntoDeclaringField(deserializedHeaders, LOGGER)); + + return deserializedHeaders; + } + + /* + * Helper method that gets the value for the given key from the cache. + */ + private static JavaType getFromCache(Type key, Function compute) { + if (TYPE_TO_JAVA_TYPE_CACHE.size() >= CACHE_SIZE_LIMIT) { + TYPE_TO_JAVA_TYPE_CACHE.clear(); + } + + return TYPE_TO_JAVA_TYPE_CACHE.computeIfAbsent(key, compute); + } +} diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionBeanPropertyWriter.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionBeanPropertyWriter.java similarity index 97% rename from sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionBeanPropertyWriter.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionBeanPropertyWriter.java index a9c95e56764c6..a44172902b929 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionBeanPropertyWriter.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionBeanPropertyWriter.java @@ -18,8 +18,9 @@ * Portions Copyright (c) Microsoft Corporation */ -package com.azure.core.implementation; +package com.azure.core.implementation.jackson; +import com.azure.core.implementation.Option; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.io.SerializedString; import com.fasterxml.jackson.databind.PropertyName; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionModule.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionModule.java similarity index 94% rename from sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionModule.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionModule.java index 3e296cbf0769f..deb87b79c90cc 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionModule.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionModule.java @@ -18,8 +18,9 @@ * Portions Copyright (c) Microsoft Corporation */ -package com.azure.core.implementation; +package com.azure.core.implementation.jackson; +import com.azure.core.implementation.Option; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.Module; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionPropertiesModifier.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionPropertiesModifier.java similarity index 95% rename from sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionPropertiesModifier.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionPropertiesModifier.java index 0b7e74dde01e3..c87fe80b6d1af 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionPropertiesModifier.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionPropertiesModifier.java @@ -18,8 +18,9 @@ * Portions Copyright (c) Microsoft Corporation */ -package com.azure.core.implementation; +package com.azure.core.implementation.jackson; +import com.azure.core.implementation.Option; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionSerializer.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionSerializer.java similarity index 97% rename from sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionSerializer.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionSerializer.java index 8b5ff9ff3d806..95f0a97be17f8 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionSerializer.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionSerializer.java @@ -18,8 +18,9 @@ * Portions Copyright (c) Microsoft Corporation */ -package com.azure.core.implementation; +package com.azure.core.implementation.jackson; +import com.azure.core.implementation.Option; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionSerializerProvider.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionSerializerProvider.java similarity index 95% rename from sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionSerializerProvider.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionSerializerProvider.java index 7176a82f8d272..1ce8007c46312 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionSerializerProvider.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionSerializerProvider.java @@ -18,8 +18,9 @@ * Portions Copyright (c) Microsoft Corporation */ -package com.azure.core.implementation; +package com.azure.core.implementation.jackson; +import com.azure.core.implementation.Option; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.MapperFeature; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionTypeModifier.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionTypeModifier.java similarity index 95% rename from sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionTypeModifier.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionTypeModifier.java index f15ac03520040..81ecb915d3f11 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/OptionTypeModifier.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/OptionTypeModifier.java @@ -18,8 +18,9 @@ * Portions Copyright (c) Microsoft Corporation */ -package com.azure.core.implementation; +package com.azure.core.implementation.jackson; +import com.azure.core.implementation.Option; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.type.ReferenceType; import com.fasterxml.jackson.databind.type.TypeBindings; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/UnixTimeDeserializer.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/UnixTimeDeserializer.java similarity index 96% rename from sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/UnixTimeDeserializer.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/UnixTimeDeserializer.java index af78c1f95d9d9..881bbc955c404 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/UnixTimeDeserializer.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/UnixTimeDeserializer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.implementation.UnixTime; import com.fasterxml.jackson.core.JsonParser; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/UnixTimeSerializer.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/UnixTimeSerializer.java similarity index 95% rename from sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/UnixTimeSerializer.java rename to sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/UnixTimeSerializer.java index fd222524f2456..42a284fdf7fa5 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/UnixTimeSerializer.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/UnixTimeSerializer.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.implementation.UnixTime; import com.fasterxml.jackson.core.JsonGenerator; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/package-info.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/package-info.java new file mode 100644 index 0000000000000..7f9652470cb4f --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/package-info.java @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/** + * Package containing all internal classes that work with Jackson + */ +package com.azure.core.implementation.jackson; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/models/CloudEvent.java b/sdk/core/azure-core/src/main/java/com/azure/core/models/CloudEvent.java index 76a4ad707911e..3ad256f9d7694 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/models/CloudEvent.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/models/CloudEvent.java @@ -4,6 +4,7 @@ package com.azure.core.models; import com.azure.core.annotation.Fluent; +import com.azure.core.implementation.jackson.ObjectMapperShim; import com.azure.core.util.BinaryData; import com.azure.core.util.logging.ClientLogger; import com.azure.core.util.serializer.JsonSerializer; @@ -14,7 +15,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -68,7 +68,7 @@ public final class CloudEvent { private static final JsonSerializer SERIALIZER = JsonSerializerProviders.createInstance(true); // May get SERIALIZER's object mapper in the future. - private static final ObjectMapper BINARY_DATA_OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapperShim BINARY_DATA_OBJECT_MAPPER = ObjectMapperShim.createDefaultMapper(); private static final Map EMPTY_ATTRIBUTES_MAP = Collections.emptyMap(); private static final TypeReference> DESERIALIZER_TYPE_REFERENCE = diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/JacksonAdapter.java b/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/JacksonAdapter.java index affff661b2211..c3cafb27759bd 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/JacksonAdapter.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/JacksonAdapter.java @@ -3,49 +3,17 @@ package com.azure.core.util.serializer; -import com.azure.core.annotation.HeaderCollection; -import com.azure.core.http.HttpHeader; import com.azure.core.http.HttpHeaders; -import com.azure.core.implementation.ReflectionUtils; -import com.azure.core.implementation.TypeUtil; +import com.azure.core.implementation.jackson.ObjectMapperShim; import com.azure.core.util.CoreUtils; import com.azure.core.util.logging.ClientLogger; -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.cfg.MapperBuilder; -import com.fasterxml.jackson.databind.json.JsonMapper; -import com.fasterxml.jackson.dataformat.xml.XmlMapper; -import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser; -import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; import java.util.regex.Pattern; /** @@ -53,124 +21,35 @@ */ public class JacksonAdapter implements SerializerAdapter { private static final Pattern PATTERN = Pattern.compile("^\"*|\"*$"); - - private static final String MUTABLE_COERCION_CONFIG = "com.fasterxml.jackson.databind.cfg.MutableCoercionConfig"; - private static final String COERCION_INPUT_SHAPE = "com.fasterxml.jackson.databind.cfg.CoercionInputShape"; - private static final String COERCION_ACTION = "com.fasterxml.jackson.databind.cfg.CoercionAction"; - - private static final MethodHandle COERCION_CONFIG_DEFAULTS; - private static final MethodHandle SET_COERCION; - private static final Object COERCION_INPUT_SHAPE_EMPTY_STRING; - private static final Object COERCION_ACTION_AS_NULL; - private static final boolean USE_REFLECTION_TO_SET_COERCION; - - static { - MethodHandles.Lookup publicLookup = MethodHandles.publicLookup(); - - MethodHandle coercionConfigDefaults = null; - MethodHandle setCoercion = null; - Object coercionInputShapeEmptyString = null; - Object coercionActionAsNull = null; - boolean useReflectionToSetCoercion = false; - - try { - Class mutableCoercionConfig = Class.forName(MUTABLE_COERCION_CONFIG); - Class coercionInputShapeClass = Class.forName(COERCION_INPUT_SHAPE); - Class coercionActionClass = Class.forName(COERCION_ACTION); - - coercionConfigDefaults = publicLookup.findVirtual(ObjectMapper.class, "coercionConfigDefaults", - MethodType.methodType(mutableCoercionConfig)); - setCoercion = publicLookup.findVirtual(mutableCoercionConfig, "setCoercion", - MethodType.methodType(mutableCoercionConfig, coercionInputShapeClass, coercionActionClass)); - coercionInputShapeEmptyString = publicLookup.findStaticGetter(coercionInputShapeClass, "EmptyString", - coercionInputShapeClass).invoke(); - coercionActionAsNull = publicLookup.findStaticGetter(coercionActionClass, "AsNull", coercionActionClass) - .invoke(); - useReflectionToSetCoercion = true; - } catch (Throwable ex) { - new ClientLogger(JacksonAdapter.class) - .verbose("Failed to retrieve MethodHandles used to set coercion configurations. " - + "Setting coercion configurations will be skipped.", ex); - } - - COERCION_CONFIG_DEFAULTS = coercionConfigDefaults; - SET_COERCION = setCoercion; - COERCION_INPUT_SHAPE_EMPTY_STRING = coercionInputShapeEmptyString; - COERCION_ACTION_AS_NULL = coercionActionAsNull; - USE_REFLECTION_TO_SET_COERCION = useReflectionToSetCoercion; - } - private final ClientLogger logger = new ClientLogger(JacksonAdapter.class); /** - * An instance of {@link ObjectMapper} to serialize/deserialize objects. + * An instance of {@link ObjectMapperShim} to serialize/deserialize objects. */ - private final ObjectMapper mapper; + private final ObjectMapperShim mapper; /** - * An instance of {@link ObjectMapper} that does not do flattening. + * An instance of {@link ObjectMapperShim} that does not do flattening. */ - private final ObjectMapper simpleMapper; + private final ObjectMapperShim simpleMapper; - private final ObjectMapper xmlMapper; + private final ObjectMapperShim xmlMapper; - private final ObjectMapper headerMapper; + private final ObjectMapperShim headerMapper; /* * The lazily-created serializer for this ServiceClient. */ private static SerializerAdapter serializerAdapter; - private static final int CACHE_SIZE_LIMIT = 10000; - - private static final Map TYPE_TO_JAVA_TYPE_CACHE = new ConcurrentHashMap<>(); - private static final Map FIELD_TO_SETTER_CACHE = new ConcurrentHashMap<>(); - /** * Creates a new JacksonAdapter instance with default mapper settings. */ public JacksonAdapter() { - this.simpleMapper = initializeMapperBuilder(JsonMapper.builder()) - .build(); - - this.headerMapper = initializeMapperBuilder(JsonMapper.builder()) - .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) - .build(); - - this.xmlMapper = initializeMapperBuilder(XmlMapper.builder()) - .defaultUseWrapper(false) - .enable(ToXmlGenerator.Feature.WRITE_XML_DECLARATION) - /* - * In Jackson 2.12 the default value of this feature changed from true to false. - * https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.12#xml-module - */ - .enable(FromXmlParser.Feature.EMPTY_ELEMENT_AS_NULL) - .build(); - - - if (USE_REFLECTION_TO_SET_COERCION) { - try { - Object object = COERCION_CONFIG_DEFAULTS.invoke(this.xmlMapper); - SET_COERCION.invoke(object, COERCION_INPUT_SHAPE_EMPTY_STRING, COERCION_ACTION_AS_NULL); - } catch (Throwable e) { - logger.verbose("Failed to set coercion actions.", e); - } - } else { - logger.verbose("Didn't set coercion defaults as it wasn't found on the classpath."); - } - - ObjectMapper flatteningMapper = initializeMapperBuilder(JsonMapper.builder()) - .addModule(FlatteningSerializer.getModule(simpleMapper())) - .addModule(FlatteningDeserializer.getModule(simpleMapper())) - .build(); - - this.mapper = initializeMapperBuilder(JsonMapper.builder()) - // Order matters: must register in reverse order of hierarchy - .addModule(AdditionalPropertiesSerializer.getModule(flatteningMapper)) - .addModule(AdditionalPropertiesDeserializer.getModule(flatteningMapper)) - .addModule(FlatteningSerializer.getModule(simpleMapper())) - .addModule(FlatteningDeserializer.getModule(simpleMapper())) - .build(); + this.simpleMapper = ObjectMapperShim.createSimpleMapper(); + this.headerMapper = ObjectMapperShim.createHeaderMapper(); + this.xmlMapper = ObjectMapperShim.createXmlMapper(); + this.mapper = ObjectMapperShim.createJsonMapper(this.simpleMapper); } /** @@ -179,7 +58,7 @@ public JacksonAdapter() { * @return an instance of {@link ObjectMapper}. */ protected ObjectMapper simpleMapper() { - return simpleMapper; + return simpleMapper.getMapper(); } /** @@ -198,6 +77,10 @@ public static synchronized SerializerAdapter createDefaultSerializerAdapter() { * @return the original serializer type */ public ObjectMapper serializer() { + return mapper.getMapper(); + } + + private ObjectMapperShim serializerShim() { return mapper; } @@ -210,7 +93,7 @@ public String serialize(Object object, SerializerEncoding encoding) throws IOExc if (encoding == SerializerEncoding.XML) { return xmlMapper.writeValueAsString(object); } else { - return serializer().writeValueAsString(object); + return serializerShim().writeValueAsString(object); } } @@ -223,7 +106,7 @@ public byte[] serializeToBytes(Object object, SerializerEncoding encoding) throw if (encoding == SerializerEncoding.XML) { return xmlMapper.writeValueAsBytes(object); } else { - return serializer().writeValueAsBytes(object); + return serializerShim() .writeValueAsBytes(object); } } @@ -236,7 +119,7 @@ public void serialize(Object object, SerializerEncoding encoding, OutputStream o if ((encoding == SerializerEncoding.XML)) { xmlMapper.writeValue(outputStream, object); } else { - serializer().writeValue(outputStream, object); + serializerShim().writeValue(outputStream, object); } } @@ -265,11 +148,10 @@ public T deserialize(String value, Type type, SerializerEncoding encoding) t return null; } - final JavaType javaType = createJavaType(type); if (encoding == SerializerEncoding.XML) { - return xmlMapper.readValue(value, javaType); + return xmlMapper.readValue(value, type); } else { - return serializer().readValue(value, javaType); + return serializerShim().readValue(value, type); } } @@ -279,11 +161,10 @@ public T deserialize(byte[] bytes, Type type, SerializerEncoding encoding) t return null; } - final JavaType javaType = createJavaType(type); if (encoding == SerializerEncoding.XML) { - return xmlMapper.readValue(bytes, javaType); + return xmlMapper.readValue(bytes, type); } else { - return serializer().readValue(bytes, javaType); + return serializerShim().readValue(bytes, type); } } @@ -294,279 +175,15 @@ public T deserialize(InputStream inputStream, final Type type, SerializerEnc return null; } - final JavaType javaType = createJavaType(type); if (encoding == SerializerEncoding.XML) { - return xmlMapper.readValue(inputStream, javaType); + return xmlMapper.readValue(inputStream, type); } else { - return serializer().readValue(inputStream, javaType); + return serializerShim().readValue(inputStream, type); } } @Override public T deserialize(HttpHeaders headers, Type deserializedHeadersType) throws IOException { - if (deserializedHeadersType == null) { - return null; - } - - T deserializedHeaders = headerMapper.convertValue(headers, createJavaType(deserializedHeadersType)); - - final Class deserializedHeadersClass = TypeUtil.getRawClass(deserializedHeadersType); - final Field[] declaredFields = deserializedHeadersClass.getDeclaredFields(); - - /* - * A list containing all handlers for header collections of the header type. - */ - final List headerCollectionHandlers = new ArrayList<>(); - - /* - * This set is an optimization where we track the first character of all HeaderCollections defined on the - * deserialized headers type. This allows us to optimize away startWiths checks which are much more costly than - * getting the first character. - */ - final Set headerCollectionsFirstCharacters = new HashSet<>(); - - /* - * Begin by looping over all declared fields and initializing all header collection information. - */ - for (final Field declaredField : declaredFields) { - if (!declaredField.isAnnotationPresent(HeaderCollection.class)) { - continue; - } - - final Type declaredFieldType = declaredField.getGenericType(); - if (!TypeUtil.isTypeOrSubTypeOf(declaredField.getType(), Map.class)) { - continue; - } - - final Type[] mapTypeArguments = TypeUtil.getTypeArguments(declaredFieldType); - if (mapTypeArguments.length != 2 - || mapTypeArguments[0] != String.class - || mapTypeArguments[1] != String.class) { - continue; - } - - final HeaderCollection headerCollectionAnnotation = declaredField.getAnnotation(HeaderCollection.class); - final String headerCollectionPrefix = headerCollectionAnnotation.value().toLowerCase(Locale.ROOT); - final int headerCollectionPrefixLength = headerCollectionPrefix.length(); - if (headerCollectionPrefixLength == 0) { - continue; - } - - headerCollectionHandlers.add(new HeaderCollectionHandler(headerCollectionPrefix, declaredField)); - headerCollectionsFirstCharacters.add(headerCollectionPrefix.charAt(0)); - } - - /* - * Then loop over all headers and check if they begin with any of the prefixes found. - */ - for (final HttpHeader header : headers) { - String headerNameLower = header.getName().toLowerCase(Locale.ROOT); - - /* - * Optimization to skip this header as it doesn't begin with any character starting header collections in - * the deserialized headers type. - */ - if (!headerCollectionsFirstCharacters.contains(headerNameLower.charAt(0))) { - continue; - } - - for (HeaderCollectionHandler headerCollectionHandler : headerCollectionHandlers) { - if (headerCollectionHandler.headerStartsWithPrefix(headerNameLower)) { - headerCollectionHandler.addHeader(header.getName(), header.getValue()); - } - } - } - - /* - * Finally, inject all found header collection values into the deserialized headers. - */ - headerCollectionHandlers.forEach(h -> h.injectValuesIntoDeclaringField(deserializedHeaders, logger)); - - return deserializedHeaders; - } - - - @SuppressWarnings("deprecation") - private static > S initializeMapperBuilder(S mapper) { - mapper.enable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS) - .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT) - .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) - .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .serializationInclusion(JsonInclude.Include.NON_NULL) - .addModule(new JavaTimeModule()) - .addModule(ByteArraySerializer.getModule()) - .addModule(Base64UrlSerializer.getModule()) - .addModule(DateTimeSerializer.getModule()) - .addModule(DateTimeDeserializer.getModule()) - .addModule(DateTimeRfc1123Serializer.getModule()) - .addModule(DurationSerializer.getModule()) - .addModule(HttpHeadersSerializer.getModule()) - .addModule(UnixTimeSerializer.getModule()) - .addModule(UnixTimeDeserializer.getModule()) - .addModule(GeoJsonSerializer.getModule()) - .addModule(GeoJsonDeserializer.getModule()) - .visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) - .visibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE) - .visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE) - .visibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.NONE); - - return mapper; - } - - private JavaType createJavaType(Type type) { - if (type == null) { - return null; - } else if (type instanceof JavaType) { - return (JavaType) type; - } else if (type instanceof ParameterizedType) { - final ParameterizedType parameterizedType = (ParameterizedType) type; - final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); - JavaType[] javaTypeArguments = new JavaType[actualTypeArguments.length]; - for (int i = 0; i != actualTypeArguments.length; i++) { - javaTypeArguments[i] = createJavaType(actualTypeArguments[i]); - } - - return getFromCache(type, TYPE_TO_JAVA_TYPE_CACHE, t -> mapper.getTypeFactory() - .constructParametricType((Class) parameterizedType.getRawType(), javaTypeArguments)); - } else { - return getFromCache(type, TYPE_TO_JAVA_TYPE_CACHE, t -> mapper.getTypeFactory().constructType(t)); - } - } - - /* - * Helper method that gets the value for the given key from the cache. - */ - private static V getFromCache(K key, Map cache, Function compute) { - if (cache.size() >= CACHE_SIZE_LIMIT) { - cache.clear(); - } - - return cache.computeIfAbsent(key, compute); - } - - /* - * Internal helper class that helps manage converting headers into their header collection. - */ - private static final class HeaderCollectionHandler { - private final String prefix; - private final int prefixLength; - private final Map values; - private final Field declaringField; - - HeaderCollectionHandler(String prefix, Field declaringField) { - this.prefix = prefix; - this.prefixLength = prefix.length(); - this.values = new HashMap<>(); - this.declaringField = declaringField; - } - - boolean headerStartsWithPrefix(String headerName) { - return headerName.startsWith(prefix); - } - - void addHeader(String headerName, String headerValue) { - values.put(headerName.substring(prefixLength), headerValue); - } - - @SuppressWarnings("deprecation") - void injectValuesIntoDeclaringField(Object deserializedHeaders, ClientLogger logger) { - /* - * First check if the deserialized headers type has a public setter. - */ - if (usePublicSetter(deserializedHeaders, logger)) { - return; - } - - /* - * Otherwise, fallback to setting the field directly. - */ - final boolean declaredFieldAccessibleBackup = declaringField.isAccessible(); - try { - if (!declaredFieldAccessibleBackup) { - AccessController.doPrivileged((PrivilegedAction) () -> { - declaringField.setAccessible(true); - return null; - }); - } - declaringField.set(deserializedHeaders, values); - logger.verbose("Set header collection by accessing the field directly."); - } catch (IllegalAccessException ex) { - logger.warning("Failed to inject header collection values into deserialized headers.", ex); - } finally { - if (!declaredFieldAccessibleBackup) { - AccessController.doPrivileged((PrivilegedAction) () -> { - declaringField.setAccessible(false); - return null; - }); - } - } - } - - private boolean usePublicSetter(Object deserializedHeaders, ClientLogger logger) { - final Class clazz = deserializedHeaders.getClass(); - final String clazzSimpleName = clazz.getSimpleName(); - final String fieldName = declaringField.getName(); - - MethodHandle setterHandler = getFromCache(declaringField, FIELD_TO_SETTER_CACHE, field -> { - MethodHandles.Lookup lookupToUse; - try { - lookupToUse = ReflectionUtils.getLookupToUse(clazz); - } catch (Throwable t) { - logger.verbose("Failed to retrieve MethodHandles.Lookup for field {}.", field, t); - return null; - } - - String setterName = getPotentialSetterName(fieldName); - - try { - MethodHandle handle = lookupToUse.findVirtual(clazz, setterName, - MethodType.methodType(clazz, Map.class)); - - logger.verbose("Using MethodHandle for setter {} on class {}.", setterName, clazzSimpleName); - - return handle; - } catch (ReflectiveOperationException ex) { - logger.verbose("Failed to retrieve MethodHandle for setter {} on class {}.", setterName, - clazzSimpleName, ex); - } - - try { - Method setterMethod = deserializedHeaders.getClass() - .getDeclaredMethod(setterName, Map.class); - MethodHandle handle = lookupToUse.unreflect(setterMethod); - - logger.verbose("Using unreflected MethodHandle for setter {} on class {}.", setterName, - clazzSimpleName); - - return handle; - } catch (ReflectiveOperationException ex) { - logger.verbose("Failed to unreflect MethodHandle for setter {} on class {}.", setterName, - clazzSimpleName, ex); - } - - return null; - }); - - if (setterHandler == null) { - return false; - } - - try { - setterHandler.invokeWithArguments(deserializedHeaders, values); - logger.verbose("Set header collection {} on class {} using MethodHandle.", fieldName, clazzSimpleName); - - return true; - } catch (Throwable ex) { - logger.verbose("Failed to set header {} collection on class {} using MethodHandle.", fieldName, - clazzSimpleName, ex); - return false; - } - } - - private static String getPotentialSetterName(String fieldName) { - return "set" + fieldName.substring(0, 1).toUpperCase(Locale.ROOT) + fieldName.substring(1); - } + return headerMapper.deserialize(headers, deserializedHeadersType); } } diff --git a/sdk/core/azure-core/src/main/java/module-info.java b/sdk/core/azure-core/src/main/java/module-info.java index fbdc6c2a747b7..1c07b3dae87df 100644 --- a/sdk/core/azure-core/src/main/java/module-info.java +++ b/sdk/core/azure-core/src/main/java/module-info.java @@ -38,6 +38,7 @@ opens com.azure.core.implementation to com.fasterxml.jackson.databind; opens com.azure.core.implementation.logging to com.fasterxml.jackson.databind; opens com.azure.core.implementation.serializer to com.fasterxml.jackson.databind; + opens com.azure.core.implementation.jackson to com.fasterxml.jackson.databind; opens com.azure.core.http.rest to com.fasterxml.jackson.databind; // Service Provider Interfaces diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/implementation/SemanticVersionTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/SemanticVersionTests.java new file mode 100644 index 0000000000000..662e7f281b6b7 --- /dev/null +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/SemanticVersionTests.java @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.implementation; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SemanticVersionTests { + @Test + public void simpleVersion() { + SemanticVersion version = SemanticVersion.parse("1.23.45"); + SemanticVersion expectedVersion = new SemanticVersion(1, 23, 45, null, "1.23.45"); + assertEquals(expectedVersion, version); + + assertEquals(1, version.getMajorVersion()); + assertEquals("1.23.45", version.getVersionString()); + assertEquals(0, version.compareTo(expectedVersion)); + assertEquals(0, version.compareTo(SemanticVersion.parse("1.23.45+build"))); + + assertEquals(1, version.compareTo(SemanticVersion.parse("1.23.45-beta"))); + assertEquals(1, version.compareTo(SemanticVersion.parse("1.23.45-beta+build"))); + assertEquals(1, version.compareTo(SemanticVersion.parse("0.23.45"))); + assertEquals(1, version.compareTo(SemanticVersion.parse("1.2.45"))); + assertEquals(1, version.compareTo(SemanticVersion.parse("1.23.4"))); + + assertEquals(-1, version.compareTo(SemanticVersion.parse("1.23.46"))); + assertEquals(-1, version.compareTo(SemanticVersion.parse("1.24.0"))); + assertEquals(-1, version.compareTo(SemanticVersion.parse("1.24.0-beta"))); + assertEquals(-1, version.compareTo(SemanticVersion.parse("1.24.0+build"))); + assertEquals(-1, version.compareTo(SemanticVersion.parse("1.24.0-beta+build"))); + assertEquals(-1, version.compareTo(SemanticVersion.parse("2.0.0"))); + assertTrue(version.isValid()); + } + + @Test + public void betaVersion() { + SemanticVersion version = SemanticVersion.parse("10.2.3-beta.1"); + SemanticVersion expectedVersion = new SemanticVersion(10, 2, 3, "beta.1", "10.2.3-beta.1"); + assertEquals(expectedVersion, version); + + assertEquals(10, version.getMajorVersion()); + assertEquals("10.2.3-beta.1", version.getVersionString()); + assertEquals(0, version.compareTo(expectedVersion)); + assertEquals(0, version.compareTo(SemanticVersion.parse("10.2.3-beta.1+build"))); + + assertEquals(1, version.compareTo(SemanticVersion.parse("10.2.3-beta.0"))); + assertEquals(1, version.compareTo(SemanticVersion.parse("10.2.2"))); + assertEquals(1, version.compareTo(SemanticVersion.parse("10.1.99"))); + assertEquals(1, version.compareTo(SemanticVersion.parse("09.2.3"))); + + assertEquals(-1, version.compareTo(SemanticVersion.parse("10.2.3-beta.2"))); + assertEquals(-1, version.compareTo(SemanticVersion.parse("10.2.4"))); + assertEquals(-1, version.compareTo(SemanticVersion.parse("10.2.4-beta"))); + assertEquals(-1, version.compareTo(SemanticVersion.parse("10.2.5"))); + assertEquals(-1, version.compareTo(SemanticVersion.parse("10.3.0"))); + assertEquals(-1, version.compareTo(SemanticVersion.parse("11.0.0"))); + + assertTrue(version.isValid()); + } + + @ParameterizedTest + @ValueSource(strings = {"nonsense", "a.b.c", "1.2", "1.2-c", "c.1.2.3", "1.2.3?beta", ""}) + public void malformedVersion(String versionStr) { + SemanticVersion malformed = SemanticVersion.parse(versionStr); + assertFalse(malformed.isValid()); + assertEquals(versionStr, malformed.getVersionString()); + assertEquals(-1, malformed.getMajorVersion()); + } + + @Test + public void reasonablyBrokenSuppoertedVersion() { + SemanticVersion brokenButSupported1 = SemanticVersion.parse("1.2.3.45"); + SemanticVersion brokenButSupported2 = SemanticVersion.parse("1.2.3.45+build"); + SemanticVersion brokenButSupported3 = SemanticVersion.parse("1.2.3.rc1"); + SemanticVersion brokenButSupported4 = SemanticVersion.parse("1.2.3.rc1+build"); + + SemanticVersion expectedVersion1 = new SemanticVersion(1, 2, 3, "45", "1.2.3.45"); + SemanticVersion expectedVersion2 = new SemanticVersion(1, 2, 3, "45", "1.2.3.45+build"); + SemanticVersion expectedVersion3 = new SemanticVersion(1, 2, 3, "rc1", "1.2.3.rc1"); + SemanticVersion expectedVersion4 = new SemanticVersion(1, 2, 3, "rc1", "1.2.3.rc1+build"); + + assertTrue(brokenButSupported1.isValid()); + assertTrue(brokenButSupported2.isValid()); + assertTrue(brokenButSupported3.isValid()); + assertTrue(brokenButSupported4.isValid()); + + assertEquals(expectedVersion1, brokenButSupported1); + assertEquals(expectedVersion2, brokenButSupported2); + assertEquals(expectedVersion3, brokenButSupported3); + assertEquals(expectedVersion4, brokenButSupported4); + + assertEquals(0, expectedVersion1.compareTo(brokenButSupported1)); + assertEquals(0, expectedVersion1.compareTo(brokenButSupported2)); + assertEquals(0, expectedVersion2.compareTo(brokenButSupported2)); + assertEquals(0, expectedVersion3.compareTo(brokenButSupported3)); + assertEquals(0, expectedVersion3.compareTo(brokenButSupported4)); + assertEquals(0, expectedVersion4.compareTo(brokenButSupported4)); + + } + + @Test + public void classVersion() { + SemanticVersion version = SemanticVersion + .getPackageVersionForClass("com.fasterxml.jackson.databind.ObjectMapper"); + assertTrue(version.isValid()); + + version = SemanticVersion.getPackageVersionForClass("org.reactivestreams.Processor"); + assertTrue(version.isValid()); + } + + @ParameterizedTest + @ValueSource(strings = {"nonsense", ""}) + public void malformedClassVersion(String className) { + SemanticVersion version = SemanticVersion.getPackageVersionForClass(className); + assertFalse(version.isValid()); + } +} diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/AdditionalPropertiesSerializerTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/AdditionalPropertiesSerializerTests.java similarity index 97% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/AdditionalPropertiesSerializerTests.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/AdditionalPropertiesSerializerTests.java index 65fc801cb1191..48fa0e2d1f79f 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/AdditionalPropertiesSerializerTests.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/AdditionalPropertiesSerializerTests.java @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; +import com.azure.core.util.serializer.JacksonAdapter; +import com.azure.core.util.serializer.SerializerEncoding; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/AdditionalPropertiesSerializerWithJacksonAnnotationTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/AdditionalPropertiesSerializerWithJacksonAnnotationTests.java similarity index 98% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/AdditionalPropertiesSerializerWithJacksonAnnotationTests.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/AdditionalPropertiesSerializerWithJacksonAnnotationTests.java index a8adf5de9c15c..b02d8bc0cbffd 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/AdditionalPropertiesSerializerWithJacksonAnnotationTests.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/AdditionalPropertiesSerializerWithJacksonAnnotationTests.java @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; +import com.azure.core.util.serializer.JacksonAdapter; +import com.azure.core.util.serializer.SerializerEncoding; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/AnimalShelter.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/AnimalShelter.java similarity index 95% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/AnimalShelter.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/AnimalShelter.java index cb5e373b6f605..b1e4d61013e31 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/AnimalShelter.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/AnimalShelter.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.annotation.JsonFlatten; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/AnimalWithTypeIdContainingDot.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/AnimalWithTypeIdContainingDot.java similarity index 95% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/AnimalWithTypeIdContainingDot.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/AnimalWithTypeIdContainingDot.java index 04b71a4c7078a..c527df0a563eb 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/AnimalWithTypeIdContainingDot.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/AnimalWithTypeIdContainingDot.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.annotation.JsonFlatten; import com.fasterxml.jackson.annotation.JsonSubTypes; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/CatWithTypeIdContainingDot.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/CatWithTypeIdContainingDot.java similarity index 95% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/CatWithTypeIdContainingDot.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/CatWithTypeIdContainingDot.java index 51ff95f412f38..30ba08eaeb914 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/CatWithTypeIdContainingDot.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/CatWithTypeIdContainingDot.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.annotation.JsonFlatten; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/ComposeTurtles.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/ComposeTurtles.java similarity index 97% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/ComposeTurtles.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/ComposeTurtles.java index dda0be7468ee0..3e02d57f23d80 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/ComposeTurtles.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/ComposeTurtles.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/DateTimeDeserializerTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/DateTimeDeserializerTests.java similarity index 94% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/DateTimeDeserializerTests.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/DateTimeDeserializerTests.java index 93aa6f4508187..0634e84083720 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/DateTimeDeserializerTests.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/DateTimeDeserializerTests.java @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; +import com.azure.core.util.serializer.JacksonAdapter; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/DateTimeSerializerTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/DateTimeSerializerTests.java similarity index 96% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/DateTimeSerializerTests.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/DateTimeSerializerTests.java index 542e1a52b111d..f26f2c6aa9637 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/DateTimeSerializerTests.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/DateTimeSerializerTests.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import org.junit.jupiter.api.Test; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/DogWithTypeIdContainingDot.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/DogWithTypeIdContainingDot.java similarity index 96% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/DogWithTypeIdContainingDot.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/DogWithTypeIdContainingDot.java index 549fefb55df0c..95b07a85b60fb 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/DogWithTypeIdContainingDot.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/DogWithTypeIdContainingDot.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.annotation.JsonFlatten; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/DurationSerializerTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/DurationSerializerTests.java similarity index 99% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/DurationSerializerTests.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/DurationSerializerTests.java index 93a8bf17aa6eb..a1c85905c199e 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/DurationSerializerTests.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/DurationSerializerTests.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import org.junit.jupiter.api.Test; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/FlattenableAnimalInfo.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/FlattenableAnimalInfo.java similarity index 94% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/FlattenableAnimalInfo.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/FlattenableAnimalInfo.java index 0f5d71b5690e0..3672d53864426 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/FlattenableAnimalInfo.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/FlattenableAnimalInfo.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/FlatteningSerializerTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/FlatteningSerializerTests.java similarity index 99% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/FlatteningSerializerTests.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/FlatteningSerializerTests.java index 637a7dad5e800..459dd15724549 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/FlatteningSerializerTests.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/FlatteningSerializerTests.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.implementation.TypeUtil; @@ -22,6 +22,8 @@ import com.azure.core.implementation.models.jsonflatten.VirtualMachineScaleSetNetworkConfiguration; import com.azure.core.implementation.models.jsonflatten.VirtualMachineScaleSetNetworkProfile; import com.azure.core.implementation.models.jsonflatten.VirtualMachineScaleSetVMProfile; +import com.azure.core.util.serializer.JacksonAdapter; +import com.azure.core.util.serializer.SerializerEncoding; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import wiremock.com.google.common.collect.ImmutableList; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/Foo.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/Foo.java similarity index 97% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/Foo.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/Foo.java index 6a372c9b80e4b..491a0b1d828f2 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/Foo.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/Foo.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.annotation.JsonFlatten; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/FooChild.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/FooChild.java similarity index 90% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/FooChild.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/FooChild.java index 2ca949edb66e9..7bea48de455a2 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/FooChild.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/FooChild.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.annotation.JsonFlatten; import com.fasterxml.jackson.annotation.JsonTypeInfo; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/GeoJsonDeserializerTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/GeoJsonDeserializerTests.java similarity index 98% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/GeoJsonDeserializerTests.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/GeoJsonDeserializerTests.java index 4352d7c2999cb..b7d61c7b515ff 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/GeoJsonDeserializerTests.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/GeoJsonDeserializerTests.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.models.GeoBoundingBox; import com.azure.core.models.GeoCollection; @@ -14,6 +14,8 @@ import com.azure.core.models.GeoPolygon; import com.azure.core.models.GeoPolygonCollection; import com.azure.core.models.GeoPosition; +import com.azure.core.util.serializer.JacksonAdapter; +import com.azure.core.util.serializer.SerializerEncoding; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/GeoJsonSerializerTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/GeoJsonSerializerTests.java similarity index 98% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/GeoJsonSerializerTests.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/GeoJsonSerializerTests.java index 4ca44244a12f7..8124cdaa04763 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/GeoJsonSerializerTests.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/GeoJsonSerializerTests.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.models.GeoBoundingBox; import com.azure.core.models.GeoCollection; @@ -15,6 +15,8 @@ import com.azure.core.models.GeoPolygon; import com.azure.core.models.GeoPolygonCollection; import com.azure.core.models.GeoPosition; +import com.azure.core.util.serializer.JacksonAdapter; +import com.azure.core.util.serializer.SerializerEncoding; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/GeoSerializationTestHelpers.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/GeoSerializationTestHelpers.java similarity index 99% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/GeoSerializationTestHelpers.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/GeoSerializationTestHelpers.java index a1fc1183882e0..ee6d7e325476e 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/GeoSerializationTestHelpers.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/GeoSerializationTestHelpers.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.implementation.GeoObjectHelper; import com.azure.core.models.GeoBoundingBox; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/NewFoo.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/NewFoo.java similarity index 98% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/NewFoo.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/NewFoo.java index 418534b0a37bb..34a7c445d83de 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/NewFoo.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/NewFoo.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.annotation.JsonFlatten; import com.fasterxml.jackson.annotation.JsonAnyGetter; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/NewFooChild.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/NewFooChild.java similarity index 91% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/NewFooChild.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/NewFooChild.java index 66e6ed03a4ea6..0cc23fe251f78 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/NewFooChild.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/NewFooChild.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.annotation.JsonFlatten; import com.fasterxml.jackson.annotation.JsonTypeInfo; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/NonEmptyAnimalWithTypeIdContainingDot.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/NonEmptyAnimalWithTypeIdContainingDot.java similarity index 95% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/NonEmptyAnimalWithTypeIdContainingDot.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/NonEmptyAnimalWithTypeIdContainingDot.java index 24aa217c768a9..02d99b41df7f2 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/NonEmptyAnimalWithTypeIdContainingDot.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/NonEmptyAnimalWithTypeIdContainingDot.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.annotation.JsonFlatten; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/implementation/OptionSerializerTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/OptionSerializerTests.java similarity index 97% rename from sdk/core/azure-core/src/test/java/com/azure/core/implementation/OptionSerializerTests.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/OptionSerializerTests.java index ec3bf84ee5174..45e534e00463c 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/implementation/OptionSerializerTests.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/OptionSerializerTests.java @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.implementation; +package com.azure.core.implementation.jackson; +import com.azure.core.implementation.Option; import com.azure.core.util.serializer.JacksonAdapter; import com.azure.core.util.serializer.SerializerEncoding; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/RabbitWithTypeIdContainingDot.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/RabbitWithTypeIdContainingDot.java similarity index 96% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/RabbitWithTypeIdContainingDot.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/RabbitWithTypeIdContainingDot.java index 514b92ff04c69..04b1704144e6f 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/RabbitWithTypeIdContainingDot.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/RabbitWithTypeIdContainingDot.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.azure.core.annotation.JsonFlatten; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/TurtleWithTypeIdContainingDot.java b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/TurtleWithTypeIdContainingDot.java similarity index 94% rename from sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/TurtleWithTypeIdContainingDot.java rename to sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/TurtleWithTypeIdContainingDot.java index cf5749d1ea664..2675044bfe54e 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/serializer/TurtleWithTypeIdContainingDot.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/implementation/jackson/TurtleWithTypeIdContainingDot.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.core.util.serializer; +package com.azure.core.implementation.jackson; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeInfo; diff --git a/sdk/core/version-matrix.json b/sdk/core/supported-version-matrix.json similarity index 86% rename from sdk/core/version-matrix.json rename to sdk/core/supported-version-matrix.json index 88240fb49ab73..5bae403b3267a 100644 --- a/sdk/core/version-matrix.json +++ b/sdk/core/supported-version-matrix.json @@ -1,5 +1,5 @@ { - "displayNames": { + "displayNames": { "2.10.5": "jackson2_10", "2.11.4": "jackson2_11", "2.12.4": "jackson2_12", @@ -12,7 +12,7 @@ "JavaTestVersion": "1.11", "AZURE_TEST_HTTP_CLIENTS": "netty", "TestGoals": "surefire:test", - "AZURE_CORE_TEST_JACKSON_VERSION": [ + "AZURE_CORE_TEST_SUPPORTED_JACKSON_VERSION": [ "2.10.5", "2.11.4", "2.12.4", @@ -20,4 +20,4 @@ ], "TestFromSource": true } -} \ No newline at end of file +} diff --git a/sdk/core/tests.yml b/sdk/core/tests.yml index 15c067662559b..355098a1f90bc 100644 --- a/sdk/core/tests.yml +++ b/sdk/core/tests.yml @@ -47,13 +47,28 @@ stages: - template: /eng/pipelines/templates/stages/archetype-sdk-tests.yml parameters: ServiceDirectory: core - TestName: 'jackson_versions' + TestName: 'jackson_supported_versions' MatrixConfigs: - - Name: jackson_version_tests - Path: sdk/core/version-matrix.json + - Name: jackson_supported_version_tests + Path: sdk/core/supported-version-matrix.json Selection: sparse NonSparseParameters: - - AZURE_CORE_TEST_JACKSON_VERSION + - AZURE_CORE_TEST_SUPPORTED_JACKSON_VERSION + GenerateVMJobs: true + Artifacts: + - name: azure-core-jackson-tests + groupId: com.azure + safeName: azurecorejacksontests + - template: /eng/pipelines/templates/stages/archetype-sdk-tests.yml + parameters: + ServiceDirectory: core + TestName: 'jackson_unsupported_versions' + MatrixConfigs: + - Name: jackson_unsupported_version_tests + Path: sdk/core/unsupported-version-matrix.json + Selection: sparse + NonSparseParameters: + - AZURE_CORE_TEST_UNSUPPORTED_JACKSON_VERSION GenerateVMJobs: true Artifacts: - name: azure-core-jackson-tests diff --git a/sdk/core/unsupported-version-matrix.json b/sdk/core/unsupported-version-matrix.json new file mode 100644 index 0000000000000..201de0d3fe235 --- /dev/null +++ b/sdk/core/unsupported-version-matrix.json @@ -0,0 +1,17 @@ +{ + "displayNames": { + "2.9.10": "jackson2_9", + }, + "matrix": { + "Agent": { + "ubuntu-20.04": { "OSVmImage": "MMSUbuntu20.04", "Pool": "azsdk-pool-mms-ubuntu-2004-general" } + }, + "JavaTestVersion": "1.11", + "AZURE_TEST_HTTP_CLIENTS": "netty", + "TestGoals": "surefire:test", + "AZURE_CORE_TEST_UNSUPPORTED_JACKSON_VERSION": [ + "2.9.10" + ], + "TestFromSource": true + } +}