From 9b035dde67eb603d86dd9d3019566f5ed0d6bf7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jun 2021 08:02:03 +0000 Subject: [PATCH 01/25] Bump mockito-inline from 3.11.1 to 3.11.2 Bumps [mockito-inline](https://github.com/mockito/mockito) from 3.11.1 to 3.11.2. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v3.11.1...v3.11.2) --- updated-dependencies: - dependency-name: org.mockito:mockito-inline dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 38c85301..325f1d7b 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -2,7 +2,7 @@ dependencies { // Use JUnit test framework testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' - testImplementation 'org.mockito:mockito-inline:3.11.1' + testImplementation 'org.mockito:mockito-inline:3.11.2' api 'com.squareup.okhttp3:okhttp:4.9.1' From 6f89ae63693b79b56f1a1d379b0d3d1892231cbe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jun 2021 08:10:44 +0000 Subject: [PATCH 02/25] Bump mockito-inline from 3.11.1 to 3.11.2 Bumps [mockito-inline](https://github.com/mockito/mockito) from 3.11.1 to 3.11.2. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v3.11.1...v3.11.2) --- updated-dependencies: - dependency-name: org.mockito:mockito-inline dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f0e1d0f8..60bb6fc0 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ org.mockito mockito-inline - 3.11.1 + 3.11.2 test From dc596a8e19ee12e226790419345477a002110763 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jul 2021 08:02:43 +0000 Subject: [PATCH 03/25] Bump azure-core from 1.17.0 to 1.18.0 Bumps [azure-core](https://github.com/Azure/azure-sdk-for-java) from 1.17.0 to 1.18.0. - [Release notes](https://github.com/Azure/azure-sdk-for-java/releases) - [Commits](https://github.com/Azure/azure-sdk-for-java/compare/azure-core_1.17.0...azure-core_1.18.0) --- updated-dependencies: - dependency-name: com.azure:azure-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gradle/dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 325f1d7b..38372fff 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -9,5 +9,5 @@ dependencies { implementation 'com.google.guava:guava:30.1.1-jre' implementation 'com.google.code.gson:gson:2.8.7' - api 'com.azure:azure-core:1.17.0' + api 'com.azure:azure-core:1.18.0' } \ No newline at end of file From 39fb4a1b2b44ff9ac10247be37213d4b123c9ec3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jul 2021 08:11:35 +0000 Subject: [PATCH 04/25] Bump azure-core from 1.17.0 to 1.18.0 Bumps [azure-core](https://github.com/Azure/azure-sdk-for-java) from 1.17.0 to 1.18.0. - [Release notes](https://github.com/Azure/azure-sdk-for-java/releases) - [Commits](https://github.com/Azure/azure-sdk-for-java/compare/azure-core_1.17.0...azure-core_1.18.0) --- updated-dependencies: - dependency-name: com.azure:azure-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 60bb6fc0..0c7ebe24 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ com.azure azure-core - 1.17.0 + 1.18.0 org.junit.jupiter From c20103424524dc0735405da4a16a7daad6436da8 Mon Sep 17 00:00:00 2001 From: PrimosK Date: Tue, 13 Jul 2021 13:37:29 +0200 Subject: [PATCH 05/25] Sprout for fix which downcasts parametrized nested `IJsonBackedObject` objects during deserialization. --- .../CollectionResponseSerializer.java | 3 +- .../graph/serializer/DefaultSerializer.java | 48 +------------ .../serializer/DerivedClassIdentifier.java | 59 ++++++++++++++++ .../FallbackTypeAdapterFactory.java | 69 +++++++++++++++++-- 4 files changed, 127 insertions(+), 52 deletions(-) create mode 100644 src/main/java/com/microsoft/graph/serializer/DerivedClassIdentifier.java diff --git a/src/main/java/com/microsoft/graph/serializer/CollectionResponseSerializer.java b/src/main/java/com/microsoft/graph/serializer/CollectionResponseSerializer.java index 2f63fb66..02baca3c 100644 --- a/src/main/java/com/microsoft/graph/serializer/CollectionResponseSerializer.java +++ b/src/main/java/com/microsoft/graph/serializer/CollectionResponseSerializer.java @@ -86,7 +86,8 @@ public static BaseCollectionResponse deserialize(@Nonnull final JsonEle for(JsonElement sourceElement : sourceArray) { if(sourceElement.isJsonObject()) { final JsonObject sourceObject = sourceElement.getAsJsonObject(); - Class entityClass = serializer.getDerivedClass(sourceObject, baseEntityClass); + final DerivedClassIdentifier derivedClassIdentifier = new DerivedClassIdentifier(logger); + Class entityClass = derivedClassIdentifier.getDerivedClass(sourceObject, baseEntityClass); if(entityClass == null) { if(baseEntityClass == null) { logger.logError("Could not find target class for object " + sourceObject.toString(), null); diff --git a/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java b/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java index 2d9d5d2e..a74b9595 100644 --- a/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java +++ b/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java @@ -22,12 +22,10 @@ package com.microsoft.graph.serializer; -import com.google.common.base.CaseFormat; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; - import com.microsoft.graph.logger.ILogger; import java.io.IOException; @@ -38,9 +36,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Map.Entry; - +import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -108,7 +105,8 @@ public T deserializeObject(@Nonnull final JsonElement rawElement, @Nonnull f // If there is a derived class, try to get it and deserialize to it T jo = jsonObject; if (rawElement.isJsonObject()) { - final Class derivedClass = this.getDerivedClass(rawObject, clazz); + final DerivedClassIdentifier derivedClassIdentifier = new DerivedClassIdentifier(logger); + final Class derivedClass = derivedClassIdentifier.getDerivedClass(rawObject, clazz); if (derivedClass != null) jo = (T) gson.fromJson(rawElement, derivedClass); } @@ -310,46 +308,6 @@ private void addAdditionalDataFromManagerToJson(AdditionalDataManager additional } } - private final static String ODATA_TYPE_KEY = "@odata.type"; - /** - * Get the derived class for the given JSON object - * This covers scenarios in which the service may return one of several derived types - * of a base object, which it defines using the odata.type parameter - * - * @param jsonObject the raw JSON object of the response - * @param parentClass the parent class the derived class should inherit from - * @return the derived class if found, or null if not applicable - */ - @Nullable - public Class getDerivedClass(@Nonnull final JsonObject jsonObject, @Nullable final Class parentClass) { - Objects.requireNonNull(jsonObject, "parameter jsonObject cannot be null"); - //Identify the odata.type information if provided - if (jsonObject.get(ODATA_TYPE_KEY) != null) { - /** #microsoft.graph.user or #microsoft.graph.callrecords.callrecord */ - final String odataType = jsonObject.get(ODATA_TYPE_KEY).getAsString(); - final int lastDotIndex = odataType.lastIndexOf("."); - final String derivedType = (odataType.substring(0, lastDotIndex) + - ".models." + - CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, - odataType.substring(lastDotIndex + 1))) - .replace("#", "com."); - try { - Class derivedClass = Class.forName(derivedType); - //Check that the derived class inherits from the given parent class - if (parentClass == null || parentClass.isAssignableFrom(derivedClass)) { - return derivedClass; - } - return null; - } catch (ClassNotFoundException e) { - logger.logDebug("Unable to find a corresponding class for derived type " + derivedType + ". Falling back to parent class."); - //If we cannot determine the derived type to cast to, return null - //This may happen if the API and the SDK are out of sync - return null; - } - } - //If there is no defined OData type, return null - return null; - } /** * Gets the logger in use diff --git a/src/main/java/com/microsoft/graph/serializer/DerivedClassIdentifier.java b/src/main/java/com/microsoft/graph/serializer/DerivedClassIdentifier.java new file mode 100644 index 00000000..dbebd998 --- /dev/null +++ b/src/main/java/com/microsoft/graph/serializer/DerivedClassIdentifier.java @@ -0,0 +1,59 @@ +package com.microsoft.graph.serializer; + +import com.google.common.base.CaseFormat; +import com.google.gson.JsonObject; +import com.microsoft.graph.logger.ILogger; + +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DerivedClassIdentifier { + + private final static String ODATA_TYPE_KEY = "@odata.type"; + private final ILogger logger; + + public DerivedClassIdentifier(ILogger logger) { + this.logger = logger; + } + + /** + * Get the derived class for the given JSON object + * This covers scenarios in which the service may return one of several derived types + * of a base object, which it defines using the odata.type parameter + * + * @param jsonObject the raw JSON object of the response + * @param parentClass the parent class the derived class should inherit from + * @return the derived class if found, or null if not applicable + */ + @Nullable + public Class getDerivedClass(@Nonnull final JsonObject jsonObject, @Nullable final Class parentClass) { + Objects.requireNonNull(jsonObject, "parameter jsonObject cannot be null"); + //Identify the odata.type information if provided + if (jsonObject.get(ODATA_TYPE_KEY) != null) { + /** #microsoft.graph.user or #microsoft.graph.callrecords.callrecord */ + final String odataType = jsonObject.get(ODATA_TYPE_KEY).getAsString(); + final int lastDotIndex = odataType.lastIndexOf("."); + final String derivedType = (odataType.substring(0, lastDotIndex) + + ".models." + + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, + odataType.substring(lastDotIndex + 1))) + .replace("#", "com."); + try { + Class derivedClass = Class.forName(derivedType); + //Check that the derived class inherits from the given parent class + if (parentClass == null || parentClass.isAssignableFrom(derivedClass)) { + return derivedClass; + } + return null; + } catch (ClassNotFoundException e) { + logger.logDebug("Unable to find a corresponding class for derived type " + derivedType + ". Falling back to parent class."); + //If we cannot determine the derived type to cast to, return null + //This may happen if the API and the SDK are out of sync + return null; + } + } + //If there is no defined OData type, return null + return null; + } +} diff --git a/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java b/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java index 9c439c65..00630a2d 100644 --- a/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java +++ b/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java @@ -22,23 +22,26 @@ package com.microsoft.graph.serializer; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - import com.google.common.base.CaseFormat; import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; +import com.google.gson.internal.Streams; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; +import com.microsoft.graph.http.BaseCollectionPage; +import com.microsoft.graph.http.BaseCollectionResponse; import com.microsoft.graph.logger.ILogger; -import javax.annotation.Nullable; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * Handles serialization/deserialization for special types (especially of @@ -89,16 +92,70 @@ public FallbackTypeAdapterFactory(@Nonnull final ILogger logger) { public TypeAdapter create(@Nonnull final Gson gson, @Nonnull final TypeToken type) { Objects.requireNonNull(type, "parameter type cannot be null"); final Class rawType = (Class) type.getRawType(); + if (rawType.isEnum()) { return new EnumTypeAdapter(rawType, logger); } else if (rawType == Void.class) { return (TypeAdapter) voidAdapter; + } else if (IJsonBackedObject.class.isAssignableFrom(type.getRawType())) { + + // Avoid overriding custom IJsonBackedObject type adapters defined in GsonFactory + if (BaseCollectionResponse.class.isAssignableFrom(rawType) || BaseCollectionPage.class.isAssignableFrom(rawType)) { + return null; + } + + final TypeAdapter delegatedAdapter = gson.getDelegateAdapter(this, type); + return (TypeAdapter) new ODataTypeParametrizedIJsonBackedObjectAdapter(gson, delegatedAdapter, type); } else { return null; } } + /** + * This adapter is responsible for deserialization of IJsonBackedObjects where service + * returns one of several derived types of a base object, which is defined using the + * odata.type parameter. If odata.type parameter is not found, the Gson default + * (delegated) type adapter is used. + */ + private class ODataTypeParametrizedIJsonBackedObjectAdapter extends TypeAdapter { + + private final Gson gson; + private final TypeAdapter delegatedAdapter; + private final TypeToken type; + + public ODataTypeParametrizedIJsonBackedObjectAdapter(@Nonnull Gson gson, @Nonnull TypeAdapter delegatedAdapter, @Nonnull final TypeToken type) { + super(); + this.gson = gson; + this.delegatedAdapter = delegatedAdapter; + this.type = type; + } + + @Override + public void write(JsonWriter out, IJsonBackedObject value) + throws IOException + { + ((TypeAdapter)this.delegatedAdapter).write(out, value); + } + + @Override + public IJsonBackedObject read(JsonReader in) { + JsonElement jsonElement = Streams.parse(in); + + if (jsonElement.isJsonObject()) { + final DerivedClassIdentifier derivedClassIdentifier = new DerivedClassIdentifier(logger); + final Class derivedClass = derivedClassIdentifier.getDerivedClass(jsonElement.getAsJsonObject(), type.getRawType()); + + if (derivedClass != null) { + final TypeAdapter subTypeAdapter = gson.getDelegateAdapter(FallbackTypeAdapterFactory.this, TypeToken.get(derivedClass)); + return (IJsonBackedObject) subTypeAdapter.fromJsonTree(jsonElement); + } + } + + return (IJsonBackedObject) delegatedAdapter.fromJsonTree(jsonElement); + } + } + private static final class EnumTypeAdapter extends TypeAdapter { private final Map enumValues; From 8b7dc5b2dee2e8ecaac6773dd7d85dae1e33c7d1 Mon Sep 17 00:00:00 2001 From: PrimosK Date: Tue, 13 Jul 2021 14:35:46 +0200 Subject: [PATCH 06/25] Bumps patch version also in pom.xml This change is related to #241 where version number in various places were bumped. It seems that the one in pom.xml should also be updated. --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0c7ebe24..f2c0d6c4 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ com.microsoft.graph microsoft-graph-core - 2.0.2 + 2.0.7 pom @@ -50,4 +50,4 @@ test - \ No newline at end of file + From 7ecec8ef4d672a3088c988c3c11e8f73cc2837c2 Mon Sep 17 00:00:00 2001 From: PrimosK Date: Tue, 13 Jul 2021 15:16:47 +0200 Subject: [PATCH 07/25] Applied proposed changes. --- .../CollectionResponseSerializer.java | 4 +- .../graph/serializer/DefaultSerializer.java | 24 ++-- .../serializer/DerivedClassIdentifier.java | 119 +++++++++--------- .../FallbackTypeAdapterFactory.java | 11 +- 4 files changed, 83 insertions(+), 75 deletions(-) diff --git a/src/main/java/com/microsoft/graph/serializer/CollectionResponseSerializer.java b/src/main/java/com/microsoft/graph/serializer/CollectionResponseSerializer.java index 02baca3c..8bba9585 100644 --- a/src/main/java/com/microsoft/graph/serializer/CollectionResponseSerializer.java +++ b/src/main/java/com/microsoft/graph/serializer/CollectionResponseSerializer.java @@ -83,11 +83,11 @@ public static BaseCollectionResponse deserialize(@Nonnull final JsonEle logger.logDebug("could not find class" + baseEntityClassCanonicalName); } try { + final DerivedClassIdentifier derivedClassIdentifier = new DerivedClassIdentifier(logger); for(JsonElement sourceElement : sourceArray) { if(sourceElement.isJsonObject()) { final JsonObject sourceObject = sourceElement.getAsJsonObject(); - final DerivedClassIdentifier derivedClassIdentifier = new DerivedClassIdentifier(logger); - Class entityClass = derivedClassIdentifier.getDerivedClass(sourceObject, baseEntityClass); + Class entityClass = derivedClassIdentifier.identify(sourceObject, baseEntityClass); if(entityClass == null) { if(baseEntityClass == null) { logger.logError("Could not find target class for object " + sourceObject.toString(), null); diff --git a/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java b/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java index a74b9595..29522015 100644 --- a/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java +++ b/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java @@ -45,17 +45,23 @@ * The default serializer implementation for the SDK */ public class DefaultSerializer implements ISerializer { - private static final String graphResponseHeadersKey = "graphResponseHeaders"; + + private static final String GRAPH_RESPONSE_HEADERS_KEY = "graphResponseHeaders"; + + /** + * The logger + */ + private final ILogger logger; /** * The instance of the internal serializer */ private final Gson gson; - /** - * The logger - */ - private final ILogger logger; + /** + * Derived class identifier + */ + private final DerivedClassIdentifier derivedClassIdentifier; /** * Creates a DefaultSerializer @@ -65,6 +71,7 @@ public class DefaultSerializer implements ISerializer { public DefaultSerializer(@Nonnull final ILogger logger) { this.logger = Objects.requireNonNull(logger, "parameter logger cannot be null"); this.gson = GsonFactory.getGsonInstance(logger); + this.derivedClassIdentifier = new DerivedClassIdentifier(logger); } @Override @@ -105,8 +112,7 @@ public T deserializeObject(@Nonnull final JsonElement rawElement, @Nonnull f // If there is a derived class, try to get it and deserialize to it T jo = jsonObject; if (rawElement.isJsonObject()) { - final DerivedClassIdentifier derivedClassIdentifier = new DerivedClassIdentifier(logger); - final Class derivedClass = derivedClassIdentifier.getDerivedClass(rawObject, clazz); + final Class derivedClass = derivedClassIdentifier.identify(rawObject, clazz); if (derivedClass != null) jo = (T) gson.fromJson(rawElement, derivedClass); } @@ -121,7 +127,7 @@ public T deserializeObject(@Nonnull final JsonElement rawElement, @Nonnull f if (responseHeaders != null) { JsonElement convertedHeaders = gson.toJsonTree(responseHeaders); - jsonBackedObject.additionalDataManager().put(graphResponseHeadersKey, convertedHeaders); + jsonBackedObject.additionalDataManager().put(GRAPH_RESPONSE_HEADERS_KEY, convertedHeaders); } return jo; } else { @@ -302,7 +308,7 @@ private void addAdditionalDataFromJsonObjectToJson (final Object item, final Jso */ private void addAdditionalDataFromManagerToJson(AdditionalDataManager additionalDataManager, JsonObject jsonNode) { for (Map.Entry entry : additionalDataManager.entrySet()) { - if(!entry.getKey().equals(graphResponseHeadersKey)) { + if(!entry.getKey().equals(GRAPH_RESPONSE_HEADERS_KEY)) { jsonNode.add(entry.getKey(), entry.getValue()); } } diff --git a/src/main/java/com/microsoft/graph/serializer/DerivedClassIdentifier.java b/src/main/java/com/microsoft/graph/serializer/DerivedClassIdentifier.java index dbebd998..6fbe1a4b 100644 --- a/src/main/java/com/microsoft/graph/serializer/DerivedClassIdentifier.java +++ b/src/main/java/com/microsoft/graph/serializer/DerivedClassIdentifier.java @@ -1,59 +1,60 @@ -package com.microsoft.graph.serializer; - -import com.google.common.base.CaseFormat; -import com.google.gson.JsonObject; -import com.microsoft.graph.logger.ILogger; - -import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public class DerivedClassIdentifier { - - private final static String ODATA_TYPE_KEY = "@odata.type"; - private final ILogger logger; - - public DerivedClassIdentifier(ILogger logger) { - this.logger = logger; - } - - /** - * Get the derived class for the given JSON object - * This covers scenarios in which the service may return one of several derived types - * of a base object, which it defines using the odata.type parameter - * - * @param jsonObject the raw JSON object of the response - * @param parentClass the parent class the derived class should inherit from - * @return the derived class if found, or null if not applicable - */ - @Nullable - public Class getDerivedClass(@Nonnull final JsonObject jsonObject, @Nullable final Class parentClass) { - Objects.requireNonNull(jsonObject, "parameter jsonObject cannot be null"); - //Identify the odata.type information if provided - if (jsonObject.get(ODATA_TYPE_KEY) != null) { - /** #microsoft.graph.user or #microsoft.graph.callrecords.callrecord */ - final String odataType = jsonObject.get(ODATA_TYPE_KEY).getAsString(); - final int lastDotIndex = odataType.lastIndexOf("."); - final String derivedType = (odataType.substring(0, lastDotIndex) + - ".models." + - CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, - odataType.substring(lastDotIndex + 1))) - .replace("#", "com."); - try { - Class derivedClass = Class.forName(derivedType); - //Check that the derived class inherits from the given parent class - if (parentClass == null || parentClass.isAssignableFrom(derivedClass)) { - return derivedClass; - } - return null; - } catch (ClassNotFoundException e) { - logger.logDebug("Unable to find a corresponding class for derived type " + derivedType + ". Falling back to parent class."); - //If we cannot determine the derived type to cast to, return null - //This may happen if the API and the SDK are out of sync - return null; - } - } - //If there is no defined OData type, return null - return null; - } -} +package com.microsoft.graph.serializer; + +import com.google.common.base.CaseFormat; +import com.google.gson.JsonObject; +import com.microsoft.graph.logger.ILogger; + +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DerivedClassIdentifier { + + private final static String ODATA_TYPE_KEY = "@odata.type"; + + private final ILogger logger; + + public DerivedClassIdentifier(@Nonnull ILogger logger) { + this.logger = Objects.requireNonNull(logger, "logger parameter cannot be null");; + } + + /** + * Get the derived class for the given JSON object + * This covers scenarios in which the service may return one of several derived types + * of a base object, which it defines using the odata.type parameter + * + * @param jsonObject the raw JSON object of the response + * @param parentClass the parent class the derived class should inherit from + * @return the derived class if found, or null if not applicable + */ + @Nullable + public Class identify(@Nonnull final JsonObject jsonObject, @Nullable final Class parentClass) { + Objects.requireNonNull(jsonObject, "parameter jsonObject cannot be null"); + //Identify the odata.type information if provided + if (jsonObject.get(ODATA_TYPE_KEY) != null) { + /** #microsoft.graph.user or #microsoft.graph.callrecords.callrecord */ + final String odataType = jsonObject.get(ODATA_TYPE_KEY).getAsString(); + final int lastDotIndex = odataType.lastIndexOf("."); + final String derivedType = (odataType.substring(0, lastDotIndex) + + ".models." + + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, + odataType.substring(lastDotIndex + 1))) + .replace("#", "com."); + try { + Class derivedClass = Class.forName(derivedType); + //Check that the derived class inherits from the given parent class + if (parentClass == null || parentClass.isAssignableFrom(derivedClass)) { + return derivedClass; + } + return null; + } catch (ClassNotFoundException e) { + logger.logDebug("Unable to find a corresponding class for derived type " + derivedType + ". Falling back to parent class."); + //If we cannot determine the derived type to cast to, return null + //This may happen if the API and the SDK are out of sync + return null; + } + } + //If there is no defined OData type, return null + return null; + } +} diff --git a/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java b/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java index 00630a2d..e0f23db4 100644 --- a/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java +++ b/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java @@ -123,12 +123,14 @@ private class ODataTypeParametrizedIJsonBackedObjectAdapter extends TypeAdapter< private final Gson gson; private final TypeAdapter delegatedAdapter; private final TypeToken type; + private final DerivedClassIdentifier derivedClassIdentifier; public ODataTypeParametrizedIJsonBackedObjectAdapter(@Nonnull Gson gson, @Nonnull TypeAdapter delegatedAdapter, @Nonnull final TypeToken type) { super(); - this.gson = gson; - this.delegatedAdapter = delegatedAdapter; - this.type = type; + this.gson = Objects.requireNonNull(gson, "parameter gson cannot be null");; + this.delegatedAdapter = Objects.requireNonNull(delegatedAdapter, "object delegated adapted cannot be null"); + this.type = Objects.requireNonNull(type, "object type cannot be null"); + this.derivedClassIdentifier = new DerivedClassIdentifier(logger); } @Override @@ -143,8 +145,7 @@ public IJsonBackedObject read(JsonReader in) { JsonElement jsonElement = Streams.parse(in); if (jsonElement.isJsonObject()) { - final DerivedClassIdentifier derivedClassIdentifier = new DerivedClassIdentifier(logger); - final Class derivedClass = derivedClassIdentifier.getDerivedClass(jsonElement.getAsJsonObject(), type.getRawType()); + final Class derivedClass = derivedClassIdentifier.identify(jsonElement.getAsJsonObject(), type.getRawType()); if (derivedClass != null) { final TypeAdapter subTypeAdapter = gson.getDelegateAdapter(FallbackTypeAdapterFactory.this, TypeToken.get(derivedClass)); From e6c9423a538a62c92ed342e202586ac647ef9712 Mon Sep 17 00:00:00 2001 From: ramsessanchez <63934382+ramsessanchez@users.noreply.github.com> Date: Wed, 14 Jul 2021 13:50:44 -0700 Subject: [PATCH 08/25] Update CODEOWNERS --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 10a1e9e1..88bfd2d0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @baywet @ddyett @MichaelMainer @nikithauc @zengin \ No newline at end of file +* @baywet @ddyett @MichaelMainer @nikithauc @zengin @ramsessanchez From 59a3098489194d6446c1830e4c231517bf7aca25 Mon Sep 17 00:00:00 2001 From: PrimosK Date: Thu, 15 Jul 2021 10:14:04 +0200 Subject: [PATCH 09/25] Fixes "Access to private field logger of class FallbackTypeAdapterFactory requires synthetic accessor". --- .../FallbackTypeAdapterFactory.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java b/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java index e0f23db4..ef8ec2d0 100644 --- a/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java +++ b/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java @@ -70,14 +70,14 @@ public void write(JsonWriter out, Void value) throws IOException { } @Override - public Void read(JsonReader in) throws IOException { + public Void read(JsonReader in) { return null; } }; /** - * Instanciates a new type adapter factory + * Instantiates a new type adapter factory * * @param logger logger to use for the factory */ @@ -94,7 +94,7 @@ public TypeAdapter create(@Nonnull final Gson gson, @Nonnull final TypeTo final Class rawType = (Class) type.getRawType(); if (rawType.isEnum()) { - return new EnumTypeAdapter(rawType, logger); + return new EnumTypeAdapter<>(rawType, logger); } else if (rawType == Void.class) { return (TypeAdapter) voidAdapter; } else if (IJsonBackedObject.class.isAssignableFrom(type.getRawType())) { @@ -104,8 +104,8 @@ public TypeAdapter create(@Nonnull final Gson gson, @Nonnull final TypeTo return null; } - final TypeAdapter delegatedAdapter = gson.getDelegateAdapter(this, type); - return (TypeAdapter) new ODataTypeParametrizedIJsonBackedObjectAdapter(gson, delegatedAdapter, type); + final TypeAdapter delegatedAdapter = (TypeAdapter) gson.getDelegateAdapter(this, type); + return (TypeAdapter) new ODataTypeParametrizedIJsonBackedObjectAdapter(gson, delegatedAdapter, (TypeToken) type, logger); } else { return null; @@ -121,13 +121,13 @@ public TypeAdapter create(@Nonnull final Gson gson, @Nonnull final TypeTo private class ODataTypeParametrizedIJsonBackedObjectAdapter extends TypeAdapter { private final Gson gson; - private final TypeAdapter delegatedAdapter; - private final TypeToken type; + private final TypeAdapter delegatedAdapter; + private final TypeToken type; private final DerivedClassIdentifier derivedClassIdentifier; - public ODataTypeParametrizedIJsonBackedObjectAdapter(@Nonnull Gson gson, @Nonnull TypeAdapter delegatedAdapter, @Nonnull final TypeToken type) { + public ODataTypeParametrizedIJsonBackedObjectAdapter(@Nonnull Gson gson, @Nonnull TypeAdapter delegatedAdapter, @Nonnull final TypeToken type, @Nonnull final ILogger logger) { super(); - this.gson = Objects.requireNonNull(gson, "parameter gson cannot be null");; + this.gson = Objects.requireNonNull(gson, "parameter gson cannot be null"); this.delegatedAdapter = Objects.requireNonNull(delegatedAdapter, "object delegated adapted cannot be null"); this.type = Objects.requireNonNull(type, "object type cannot be null"); this.derivedClassIdentifier = new DerivedClassIdentifier(logger); @@ -137,7 +137,7 @@ public ODataTypeParametrizedIJsonBackedObjectAdapter(@Nonnull Gson gson, @Nonnul public void write(JsonWriter out, IJsonBackedObject value) throws IOException { - ((TypeAdapter)this.delegatedAdapter).write(out, value); + this.delegatedAdapter.write(out, value); } @Override @@ -153,7 +153,7 @@ public IJsonBackedObject read(JsonReader in) { } } - return (IJsonBackedObject) delegatedAdapter.fromJsonTree(jsonElement); + return delegatedAdapter.fromJsonTree(jsonElement); } } From ba9993b8805505481b182a2bb71f996bf275e8ea Mon Sep 17 00:00:00 2001 From: PrimosK Date: Mon, 19 Jul 2021 08:25:10 +0200 Subject: [PATCH 10/25] Improves the logic which makes sure that custom IJsonBackedObject type adapters defined in GsonFactory are not overridden. --- .../graph/serializer/FallbackTypeAdapterFactory.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java b/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java index ef8ec2d0..549f28d0 100644 --- a/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java +++ b/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java @@ -28,12 +28,11 @@ import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; import com.google.gson.internal.Streams; +import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; -import com.microsoft.graph.http.BaseCollectionPage; -import com.microsoft.graph.http.BaseCollectionResponse; import com.microsoft.graph.logger.ILogger; import java.io.IOException; @@ -99,12 +98,13 @@ public TypeAdapter create(@Nonnull final Gson gson, @Nonnull final TypeTo return (TypeAdapter) voidAdapter; } else if (IJsonBackedObject.class.isAssignableFrom(type.getRawType())) { + final TypeAdapter delegatedAdapter = (TypeAdapter) gson.getDelegateAdapter(this, type); + // Avoid overriding custom IJsonBackedObject type adapters defined in GsonFactory - if (BaseCollectionResponse.class.isAssignableFrom(rawType) || BaseCollectionPage.class.isAssignableFrom(rawType)) { + if (!(delegatedAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) { return null; } - final TypeAdapter delegatedAdapter = (TypeAdapter) gson.getDelegateAdapter(this, type); return (TypeAdapter) new ODataTypeParametrizedIJsonBackedObjectAdapter(gson, delegatedAdapter, (TypeToken) type, logger); } else { From 1305d4247d16c34413a130f40f1ae6aef72d3ac9 Mon Sep 17 00:00:00 2001 From: PrimosK Date: Mon, 19 Jul 2021 08:55:15 +0200 Subject: [PATCH 11/25] Added unit test covering case in PR #249. --- .../microsoft/graph/models/MessageStub.java | 20 +++++++ .../MessagesCollectionResponseStub.java | 10 ++++ .../microsoft/graph/models/ReactionStub.java | 21 ++++++++ .../graph/models/SubReactionStub1.java | 10 ++++ .../graph/models/SubReactionStub2.java | 10 ++++ .../graph/models/TestIJsonBackedObject.java | 24 +++++++++ .../FallbackTypeAdapterFactoryTest.java | 54 +++++++++++++++++++ 7 files changed, 149 insertions(+) create mode 100644 src/test/java/com/microsoft/graph/models/MessageStub.java create mode 100644 src/test/java/com/microsoft/graph/models/MessagesCollectionResponseStub.java create mode 100644 src/test/java/com/microsoft/graph/models/ReactionStub.java create mode 100644 src/test/java/com/microsoft/graph/models/SubReactionStub1.java create mode 100644 src/test/java/com/microsoft/graph/models/SubReactionStub2.java create mode 100644 src/test/java/com/microsoft/graph/models/TestIJsonBackedObject.java create mode 100644 src/test/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactoryTest.java diff --git a/src/test/java/com/microsoft/graph/models/MessageStub.java b/src/test/java/com/microsoft/graph/models/MessageStub.java new file mode 100644 index 00000000..886bcd21 --- /dev/null +++ b/src/test/java/com/microsoft/graph/models/MessageStub.java @@ -0,0 +1,20 @@ +package com.microsoft.graph.models; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +/** + * This class shouldn't be moved as it's location in this + * particular package is tightly coupled with the logic present at: + * com/microsoft/graph/serializer/DerivedClassIdentifier.java:38 + */ +public class MessageStub extends TestIJsonBackedObject { + + @SerializedName("name") + @Expose() + public String name; + + @SerializedName("reaction") + @Expose() + public ReactionStub reaction; +} diff --git a/src/test/java/com/microsoft/graph/models/MessagesCollectionResponseStub.java b/src/test/java/com/microsoft/graph/models/MessagesCollectionResponseStub.java new file mode 100644 index 00000000..b3041834 --- /dev/null +++ b/src/test/java/com/microsoft/graph/models/MessagesCollectionResponseStub.java @@ -0,0 +1,10 @@ +package com.microsoft.graph.models; + +import com.microsoft.graph.http.BaseCollectionResponse; + +public class MessagesCollectionResponseStub extends BaseCollectionResponse { + + public MessagesCollectionResponseStub() { + } + +} diff --git a/src/test/java/com/microsoft/graph/models/ReactionStub.java b/src/test/java/com/microsoft/graph/models/ReactionStub.java new file mode 100644 index 00000000..bdcfb63b --- /dev/null +++ b/src/test/java/com/microsoft/graph/models/ReactionStub.java @@ -0,0 +1,21 @@ +package com.microsoft.graph.models; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; +import org.jetbrains.annotations.Nullable; + +/** + * This class shouldn't be moved as it's location in this + * particular package is tightly coupled with the logic present at: + * com/microsoft/graph/serializer/DerivedClassIdentifier.java:38 + */ +public class ReactionStub extends TestIJsonBackedObject { + + /** + * the OData type of the object as returned by the service + */ + @SerializedName("@odata.type") + @Expose + @Nullable + public String oDataType; +} diff --git a/src/test/java/com/microsoft/graph/models/SubReactionStub1.java b/src/test/java/com/microsoft/graph/models/SubReactionStub1.java new file mode 100644 index 00000000..c2e7cc05 --- /dev/null +++ b/src/test/java/com/microsoft/graph/models/SubReactionStub1.java @@ -0,0 +1,10 @@ +package com.microsoft.graph.models; + +/** + * This class shouldn't be moved as it's location in this + * particular package is tightly coupled with the logic present at: + * com/microsoft/graph/serializer/DerivedClassIdentifier.java:38 + */ +public class SubReactionStub1 extends ReactionStub { + +} diff --git a/src/test/java/com/microsoft/graph/models/SubReactionStub2.java b/src/test/java/com/microsoft/graph/models/SubReactionStub2.java new file mode 100644 index 00000000..33246dbd --- /dev/null +++ b/src/test/java/com/microsoft/graph/models/SubReactionStub2.java @@ -0,0 +1,10 @@ +package com.microsoft.graph.models; + +/** + * This class shouldn't be moved as it's location in this + * particular package is tightly coupled with the logic present at: + * com/microsoft/graph/serializer/DerivedClassIdentifier.java:38 + */ +public class SubReactionStub2 extends ReactionStub { + +} diff --git a/src/test/java/com/microsoft/graph/models/TestIJsonBackedObject.java b/src/test/java/com/microsoft/graph/models/TestIJsonBackedObject.java new file mode 100644 index 00000000..bd0c4a13 --- /dev/null +++ b/src/test/java/com/microsoft/graph/models/TestIJsonBackedObject.java @@ -0,0 +1,24 @@ +package com.microsoft.graph.models; + +import com.google.gson.JsonObject; +import com.microsoft.graph.serializer.AdditionalDataManager; +import com.microsoft.graph.serializer.IJsonBackedObject; +import com.microsoft.graph.serializer.ISerializer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static org.mockito.Mockito.mock; + +public abstract class TestIJsonBackedObject implements IJsonBackedObject { + + @Override + public void setRawObject(@NotNull ISerializer serializer, @NotNull JsonObject json) { + // Do nothing + } + + @Nullable + @Override + public AdditionalDataManager additionalDataManager() { + return mock(AdditionalDataManager.class); + } +} diff --git a/src/test/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactoryTest.java b/src/test/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactoryTest.java new file mode 100644 index 00000000..2200023a --- /dev/null +++ b/src/test/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactoryTest.java @@ -0,0 +1,54 @@ +package com.microsoft.graph.serializer; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import com.microsoft.graph.http.BaseCollectionResponse; +import com.microsoft.graph.logger.ILogger; +import com.microsoft.graph.models.MessageStub; +import com.microsoft.graph.models.MessagesCollectionResponseStub; +import com.microsoft.graph.models.SubReactionStub1; +import com.microsoft.graph.models.SubReactionStub2; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Type; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +public class FallbackTypeAdapterFactoryTest { + + /** + * This test covers the scenario of objects not being + * properly deserialized (@odata.type not taken into account): + * https://github.com/microsoftgraph/msgraph-sdk-java-core/pull/249 + */ + @Test + public void testDeserializationOfNestedODataTypeAnnotatedObjects() { + final ILogger logger = mock(ILogger.class); + final Gson gsonInstance = GsonFactory.getGsonInstance(logger); + + final Type listType = new TypeToken(){}.getType(); + final String testJsonResponse = + "{\"value\": " + + " [" + + " {\"name\": \"parent1\",\"reaction\": {\"@odata.type\": \"#microsoft.graph.subReactionStub1\"}}," + + " {\"name\": \"parent2\",\"reaction\": {\"@odata.type\": \"#microsoft.graph.subReactionStub2\"}}" + + " ]" + + "}"; + + final BaseCollectionResponse baseCollectionResponse = gsonInstance.fromJson(testJsonResponse, listType); + + assertNotNull(baseCollectionResponse.value); + + final MessageStub messageStub1 = baseCollectionResponse.value.get(0); + final MessageStub messageStub2 = baseCollectionResponse.value.get(1); + + assertTrue(messageStub1.reaction instanceof SubReactionStub1); + assertTrue(messageStub2.reaction instanceof SubReactionStub2); + } + +} From 3b6be7c395cef0d7106b04f4aac21f034486c1da Mon Sep 17 00:00:00 2001 From: PrimosK Date: Wed, 21 Jul 2021 07:29:01 +0200 Subject: [PATCH 12/25] Moved `ODataTypeParametrizedIJsonBackedObjectAdapter` to it's own file. --- ...peParametrizedIJsonBackedTypedAdapter.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/main/java/com/microsoft/graph/serializer/ODataTypeParametrizedIJsonBackedTypedAdapter.java diff --git a/src/main/java/com/microsoft/graph/serializer/ODataTypeParametrizedIJsonBackedTypedAdapter.java b/src/main/java/com/microsoft/graph/serializer/ODataTypeParametrizedIJsonBackedTypedAdapter.java new file mode 100644 index 00000000..4b2620d5 --- /dev/null +++ b/src/main/java/com/microsoft/graph/serializer/ODataTypeParametrizedIJsonBackedTypedAdapter.java @@ -0,0 +1,63 @@ +package com.microsoft.graph.serializer; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.TypeAdapter; +import com.google.gson.internal.Streams; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.microsoft.graph.logger.ILogger; + +import java.io.IOException; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * This adapter is responsible for deserialization of IJsonBackedObjects where service + * returns one of several derived types of a base object, which is defined using the + * odata.type parameter. If odata.type parameter is not found, the Gson default + * (delegated) type adapter is used. + */ +class ODataTypeParametrizedIJsonBackedTypedAdapter extends TypeAdapter { + + private final FallbackTypeAdapterFactory fallbackTypeAdapterFactory; + private final Gson gson; + private final TypeAdapter delegatedAdapter; + private final TypeToken type; + private final DerivedClassIdentifier derivedClassIdentifier; + + public ODataTypeParametrizedIJsonBackedTypedAdapter(FallbackTypeAdapterFactory fallbackTypeAdapterFactory, @Nonnull Gson gson, + @Nonnull TypeAdapter delegatedAdapter, @Nonnull final TypeToken type, @Nonnull final ILogger logger) + { + super(); + this.fallbackTypeAdapterFactory = fallbackTypeAdapterFactory; + this.gson = Objects.requireNonNull(gson, "parameter gson cannot be null"); + this.delegatedAdapter = Objects.requireNonNull(delegatedAdapter, "object delegated adapted cannot be null"); + this.type = Objects.requireNonNull(type, "object type cannot be null"); + this.derivedClassIdentifier = new DerivedClassIdentifier(logger); + } + + @Override + public void write(JsonWriter out, IJsonBackedObject value) + throws IOException + { + this.delegatedAdapter.write(out, value); + } + + @Override + public IJsonBackedObject read(JsonReader in) { + JsonElement jsonElement = Streams.parse(in); + + if (jsonElement.isJsonObject()) { + final Class derivedClass = derivedClassIdentifier.identify(jsonElement.getAsJsonObject(), type.getRawType()); + + if (derivedClass != null) { + final TypeAdapter subTypeAdapter = gson.getDelegateAdapter(fallbackTypeAdapterFactory, TypeToken.get(derivedClass)); + return (IJsonBackedObject) subTypeAdapter.fromJsonTree(jsonElement); + } + } + + return delegatedAdapter.fromJsonTree(jsonElement); + } +} From 88eca4232605bdecc16b0b026a5a31702a33d0e6 Mon Sep 17 00:00:00 2001 From: PrimosK Date: Wed, 21 Jul 2021 07:45:21 +0200 Subject: [PATCH 13/25] Increased unit tests coverage. --- .../FallbackTypeAdapterFactory.java | 49 +-------- .../microsoft/graph/models/MessageStub.java | 4 +- .../microsoft/graph/models/ReactionsStub.java | 21 ++++ .../FallbackTypeAdapterFactoryTest.java | 54 ---------- ...rametrizedIJsonBackedTypedAdapterTest.java | 101 ++++++++++++++++++ 5 files changed, 125 insertions(+), 104 deletions(-) create mode 100644 src/test/java/com/microsoft/graph/models/ReactionsStub.java delete mode 100644 src/test/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactoryTest.java create mode 100644 src/test/java/com/microsoft/graph/serializer/ODataTypeParametrizedIJsonBackedTypedAdapterTest.java diff --git a/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java b/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java index 549f28d0..849e8750 100644 --- a/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java +++ b/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java @@ -24,10 +24,8 @@ import com.google.common.base.CaseFormat; import com.google.gson.Gson; -import com.google.gson.JsonElement; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; -import com.google.gson.internal.Streams; import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; @@ -105,58 +103,13 @@ public TypeAdapter create(@Nonnull final Gson gson, @Nonnull final TypeTo return null; } - return (TypeAdapter) new ODataTypeParametrizedIJsonBackedObjectAdapter(gson, delegatedAdapter, (TypeToken) type, logger); + return (TypeAdapter) new ODataTypeParametrizedIJsonBackedTypedAdapter(this, gson, delegatedAdapter, (TypeToken) type, logger); } else { return null; } } - /** - * This adapter is responsible for deserialization of IJsonBackedObjects where service - * returns one of several derived types of a base object, which is defined using the - * odata.type parameter. If odata.type parameter is not found, the Gson default - * (delegated) type adapter is used. - */ - private class ODataTypeParametrizedIJsonBackedObjectAdapter extends TypeAdapter { - - private final Gson gson; - private final TypeAdapter delegatedAdapter; - private final TypeToken type; - private final DerivedClassIdentifier derivedClassIdentifier; - - public ODataTypeParametrizedIJsonBackedObjectAdapter(@Nonnull Gson gson, @Nonnull TypeAdapter delegatedAdapter, @Nonnull final TypeToken type, @Nonnull final ILogger logger) { - super(); - this.gson = Objects.requireNonNull(gson, "parameter gson cannot be null"); - this.delegatedAdapter = Objects.requireNonNull(delegatedAdapter, "object delegated adapted cannot be null"); - this.type = Objects.requireNonNull(type, "object type cannot be null"); - this.derivedClassIdentifier = new DerivedClassIdentifier(logger); - } - - @Override - public void write(JsonWriter out, IJsonBackedObject value) - throws IOException - { - this.delegatedAdapter.write(out, value); - } - - @Override - public IJsonBackedObject read(JsonReader in) { - JsonElement jsonElement = Streams.parse(in); - - if (jsonElement.isJsonObject()) { - final Class derivedClass = derivedClassIdentifier.identify(jsonElement.getAsJsonObject(), type.getRawType()); - - if (derivedClass != null) { - final TypeAdapter subTypeAdapter = gson.getDelegateAdapter(FallbackTypeAdapterFactory.this, TypeToken.get(derivedClass)); - return (IJsonBackedObject) subTypeAdapter.fromJsonTree(jsonElement); - } - } - - return delegatedAdapter.fromJsonTree(jsonElement); - } - } - private static final class EnumTypeAdapter extends TypeAdapter { private final Map enumValues; diff --git a/src/test/java/com/microsoft/graph/models/MessageStub.java b/src/test/java/com/microsoft/graph/models/MessageStub.java index 886bcd21..b6d6621a 100644 --- a/src/test/java/com/microsoft/graph/models/MessageStub.java +++ b/src/test/java/com/microsoft/graph/models/MessageStub.java @@ -10,9 +10,9 @@ */ public class MessageStub extends TestIJsonBackedObject { - @SerializedName("name") + @SerializedName("body") @Expose() - public String name; + public String body; @SerializedName("reaction") @Expose() diff --git a/src/test/java/com/microsoft/graph/models/ReactionsStub.java b/src/test/java/com/microsoft/graph/models/ReactionsStub.java new file mode 100644 index 00000000..458bda2c --- /dev/null +++ b/src/test/java/com/microsoft/graph/models/ReactionsStub.java @@ -0,0 +1,21 @@ +package com.microsoft.graph.models; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * This class shouldn't be moved as it's location in this + * particular package is tightly coupled with the logic present at: + * com/microsoft/graph/serializer/DerivedClassIdentifier.java:38 + */ +public class ReactionsStub extends TestIJsonBackedObject { + + @SerializedName("reactions") + @Expose + @Nullable + public List reactions; + +} diff --git a/src/test/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactoryTest.java b/src/test/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactoryTest.java deleted file mode 100644 index 2200023a..00000000 --- a/src/test/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactoryTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.microsoft.graph.serializer; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.reflect.TypeToken; -import com.microsoft.graph.http.BaseCollectionResponse; -import com.microsoft.graph.logger.ILogger; -import com.microsoft.graph.models.MessageStub; -import com.microsoft.graph.models.MessagesCollectionResponseStub; -import com.microsoft.graph.models.SubReactionStub1; -import com.microsoft.graph.models.SubReactionStub2; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Type; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; - -public class FallbackTypeAdapterFactoryTest { - - /** - * This test covers the scenario of objects not being - * properly deserialized (@odata.type not taken into account): - * https://github.com/microsoftgraph/msgraph-sdk-java-core/pull/249 - */ - @Test - public void testDeserializationOfNestedODataTypeAnnotatedObjects() { - final ILogger logger = mock(ILogger.class); - final Gson gsonInstance = GsonFactory.getGsonInstance(logger); - - final Type listType = new TypeToken(){}.getType(); - final String testJsonResponse = - "{\"value\": " + - " [" + - " {\"name\": \"parent1\",\"reaction\": {\"@odata.type\": \"#microsoft.graph.subReactionStub1\"}}," + - " {\"name\": \"parent2\",\"reaction\": {\"@odata.type\": \"#microsoft.graph.subReactionStub2\"}}" + - " ]" + - "}"; - - final BaseCollectionResponse baseCollectionResponse = gsonInstance.fromJson(testJsonResponse, listType); - - assertNotNull(baseCollectionResponse.value); - - final MessageStub messageStub1 = baseCollectionResponse.value.get(0); - final MessageStub messageStub2 = baseCollectionResponse.value.get(1); - - assertTrue(messageStub1.reaction instanceof SubReactionStub1); - assertTrue(messageStub2.reaction instanceof SubReactionStub2); - } - -} diff --git a/src/test/java/com/microsoft/graph/serializer/ODataTypeParametrizedIJsonBackedTypedAdapterTest.java b/src/test/java/com/microsoft/graph/serializer/ODataTypeParametrizedIJsonBackedTypedAdapterTest.java new file mode 100644 index 00000000..e235941f --- /dev/null +++ b/src/test/java/com/microsoft/graph/serializer/ODataTypeParametrizedIJsonBackedTypedAdapterTest.java @@ -0,0 +1,101 @@ +package com.microsoft.graph.serializer; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.microsoft.graph.http.BaseCollectionResponse; +import com.microsoft.graph.logger.ILogger; +import com.microsoft.graph.models.MessageStub; +import com.microsoft.graph.models.MessagesCollectionResponseStub; +import com.microsoft.graph.models.ReactionStub; +import com.microsoft.graph.models.ReactionsStub; +import com.microsoft.graph.models.SubReactionStub1; +import com.microsoft.graph.models.SubReactionStub2; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Type; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +/** + * Covers the scenario of objects not being properly deserialized + * (@odata.type not taken into account): + * https://github.com/microsoftgraph/msgraph-sdk-java-core/pull/249 + */ +public class ODataTypeParametrizedIJsonBackedTypedAdapterTest { + + final ILogger logger = mock(ILogger.class); + final Gson gsonInstance = GsonFactory.getGsonInstance(logger); + + @Test + public void testDeserializationOfObjectWithODataTypeProperty() { + // Given + final String testJsonResponse = + "{\"@odata.type\": \"#microsoft.graph.subReactionStub1\"}"; + + // When + ReactionStub reaction = gsonInstance.fromJson(testJsonResponse, ReactionStub.class); + + // Then + assertTrue(reaction instanceof SubReactionStub1); + } + + @Test + public void testDeserializationOfPropertyWithODataTypeProperty() { + // Given + final String testJsonResponse = + "{\"body\": \"message1\",\"reaction\": {\"@odata.type\": \"#microsoft.graph.subReactionStub2\"}}"; + + // When + MessageStub message = gsonInstance.fromJson(testJsonResponse, MessageStub.class); + + // Then + assertTrue(message.reaction instanceof SubReactionStub2); + } + + @Test + public void testDeserializationOfCollectionPropertyContainingObjectsWithODataTypeProperty() { + // Given + final String testJsonResponse = + "{ reactions : [" + + "{\"@odata.type\": \"#microsoft.graph.subReactionStub1\"}," + + "{\"@odata.type\": \"#microsoft.graph.subReactionStub2\"}," + + "{\"@odata.type\": \"#microsoft.graph.subReactionStub1\"}," + + "]}"; + + // When + ReactionsStub reactions = gsonInstance.fromJson(testJsonResponse, ReactionsStub.class); + + // Then + assertNotNull(reactions.reactions); + assertTrue(reactions.reactions.get(0) instanceof SubReactionStub1); + assertTrue(reactions.reactions.get(1) instanceof SubReactionStub2); + assertTrue(reactions.reactions.get(2) instanceof SubReactionStub1); + } + + @Test + public void testDeserializationOfNestedODataTypeAnnotatedObjects() { + // Given + final Type listType = new TypeToken(){}.getType(); + final String testJsonResponse = + "{\"value\": " + + " [" + + " {\"body\": \"message1\",\"reaction\": {\"@odata.type\": \"#microsoft.graph.subReactionStub1\"}}," + + " {\"body\": \"message2\",\"reaction\": {\"@odata.type\": \"#microsoft.graph.subReactionStub2\"}}" + + " ]" + + "}"; + + // When + final BaseCollectionResponse baseCollectionResponse = gsonInstance.fromJson(testJsonResponse, listType); + + // Then + assertNotNull(baseCollectionResponse.value); + + final MessageStub messageStub1 = baseCollectionResponse.value.get(0); + final MessageStub messageStub2 = baseCollectionResponse.value.get(1); + + assertTrue(messageStub1.reaction instanceof SubReactionStub1); + assertTrue(messageStub2.reaction instanceof SubReactionStub2); + } +} From 69df140e0a96d79a0e0712e17f8a7859d2569fce Mon Sep 17 00:00:00 2001 From: PrimosK Date: Wed, 21 Jul 2021 11:04:23 +0200 Subject: [PATCH 14/25] Removed DerivedClassIdentifier and related code from: - DefaultSerializer - CollectionResponseDeserializer ... as this is now taken care by ODataTypeParametrizedJsonBackedTypeAdapter. Tests covering this change: - com.microsoft.graph.serializer.DefaultSerializerTest.testDeserializationOfObjectWithODataTypeProperty - com.microsoft.graph.serializer.ODataTypeParametrizedIJsonBackedTypedAdapterTest.testDeserializationOfNestedODataTypeAnnotatedObjects --- .../serializer/CollectionPageSerializer.java | 3 +- ...va => CollectionResponseDeserializer.java} | 17 +++------ .../graph/serializer/DefaultSerializer.java | 18 ++-------- .../graph/serializer/GsonFactory.java | 2 +- .../graph/models/TestIJsonBackedObject.java | 12 +++++-- .../serializer/DefaultSerializerTest.java | 36 +++++++++++++++++++ 6 files changed, 54 insertions(+), 34 deletions(-) rename src/main/java/com/microsoft/graph/serializer/{CollectionResponseSerializer.java => CollectionResponseDeserializer.java} (87%) create mode 100644 src/test/java/com/microsoft/graph/serializer/DefaultSerializerTest.java diff --git a/src/main/java/com/microsoft/graph/serializer/CollectionPageSerializer.java b/src/main/java/com/microsoft/graph/serializer/CollectionPageSerializer.java index dd305b53..424503af 100644 --- a/src/main/java/com/microsoft/graph/serializer/CollectionPageSerializer.java +++ b/src/main/java/com/microsoft/graph/serializer/CollectionPageSerializer.java @@ -24,7 +24,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Type; -import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -112,7 +111,7 @@ public static > BaseCollectionPage final Class responseClass = Class.forName(responseClassCanonicalName); final JsonObject responseJson = new JsonObject(); responseJson.add("value", json); - final BaseCollectionResponse response = CollectionResponseSerializer.deserialize(responseJson, responseClass, logger); + final BaseCollectionResponse response = CollectionResponseDeserializer.deserialize(responseJson, responseClass, logger); /** eg: com.microsoft.graph.requests.AttachmentCollectionRequestBuilder */ final String responseBuilderCanonicalName = responseClassCanonicalName .substring(0, responseClassCanonicalName.length() - responseLength) + "RequestBuilder"; diff --git a/src/main/java/com/microsoft/graph/serializer/CollectionResponseSerializer.java b/src/main/java/com/microsoft/graph/serializer/CollectionResponseDeserializer.java similarity index 87% rename from src/main/java/com/microsoft/graph/serializer/CollectionResponseSerializer.java rename to src/main/java/com/microsoft/graph/serializer/CollectionResponseDeserializer.java index 8bba9585..5b9c5c5a 100644 --- a/src/main/java/com/microsoft/graph/serializer/CollectionResponseSerializer.java +++ b/src/main/java/com/microsoft/graph/serializer/CollectionResponseDeserializer.java @@ -37,13 +37,13 @@ import com.microsoft.graph.http.BaseCollectionResponse; import com.microsoft.graph.logger.ILogger; -/** Specialized serializer to handle collection responses */ -public class CollectionResponseSerializer { +/** Specialized de-serializer to handle collection responses */ +public class CollectionResponseDeserializer { private static DefaultSerializer serializer; /** * Not available for instantiation */ - private CollectionResponseSerializer() {} + private CollectionResponseDeserializer() {} /** * Deserializes the JsonElement * @@ -83,19 +83,10 @@ public static BaseCollectionResponse deserialize(@Nonnull final JsonEle logger.logDebug("could not find class" + baseEntityClassCanonicalName); } try { - final DerivedClassIdentifier derivedClassIdentifier = new DerivedClassIdentifier(logger); for(JsonElement sourceElement : sourceArray) { if(sourceElement.isJsonObject()) { final JsonObject sourceObject = sourceElement.getAsJsonObject(); - Class entityClass = derivedClassIdentifier.identify(sourceObject, baseEntityClass); - if(entityClass == null) { - if(baseEntityClass == null) { - logger.logError("Could not find target class for object " + sourceObject.toString(), null); - continue; - } else - entityClass = baseEntityClass; // it is possible the odata type is absent or we can't find the derived type (not in SDK yet) - } - final T1 targetObject = (T1)serializer.deserializeObject(sourceObject, entityClass); + final T1 targetObject = (T1)serializer.deserializeObject(sourceObject, baseEntityClass); ((IJsonBackedObject)targetObject).setRawObject(serializer, sourceObject); list.add(targetObject); } else if (sourceElement.isJsonPrimitive()) { diff --git a/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java b/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java index 29522015..3ea6cf36 100644 --- a/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java +++ b/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java @@ -58,10 +58,6 @@ public class DefaultSerializer implements ISerializer { */ private final Gson gson; - /** - * Derived class identifier - */ - private final DerivedClassIdentifier derivedClassIdentifier; /** * Creates a DefaultSerializer @@ -71,7 +67,6 @@ public class DefaultSerializer implements ISerializer { public DefaultSerializer(@Nonnull final ILogger logger) { this.logger = Objects.requireNonNull(logger, "parameter logger cannot be null"); this.gson = GsonFactory.getGsonInstance(logger); - this.derivedClassIdentifier = new DerivedClassIdentifier(logger); } @Override @@ -108,16 +103,7 @@ public T deserializeObject(@Nonnull final JsonElement rawElement, @Nonnull f if (jsonObject instanceof IJsonBackedObject) { logger.logDebug("Deserializing type " + clazz.getSimpleName()); final JsonObject rawObject = rawElement.isJsonObject() ? rawElement.getAsJsonObject() : null; - - // If there is a derived class, try to get it and deserialize to it - T jo = jsonObject; - if (rawElement.isJsonObject()) { - final Class derivedClass = derivedClassIdentifier.identify(rawObject, clazz); - if (derivedClass != null) - jo = (T) gson.fromJson(rawElement, derivedClass); - } - - final IJsonBackedObject jsonBackedObject = (IJsonBackedObject) jo; + final IJsonBackedObject jsonBackedObject = (IJsonBackedObject) jsonObject; if(rawElement.isJsonObject()) { jsonBackedObject.setRawObject(this, rawObject); @@ -129,7 +115,7 @@ public T deserializeObject(@Nonnull final JsonElement rawElement, @Nonnull f JsonElement convertedHeaders = gson.toJsonTree(responseHeaders); jsonBackedObject.additionalDataManager().put(GRAPH_RESPONSE_HEADERS_KEY, convertedHeaders); } - return jo; + return jsonObject; } else { logger.logDebug("Deserializing a non-IJsonBackedObject type " + clazz.getSimpleName()); return jsonObject; diff --git a/src/main/java/com/microsoft/graph/serializer/GsonFactory.java b/src/main/java/com/microsoft/graph/serializer/GsonFactory.java index 4a040cc9..1b24e1c0 100644 --- a/src/main/java/com/microsoft/graph/serializer/GsonFactory.java +++ b/src/main/java/com/microsoft/graph/serializer/GsonFactory.java @@ -239,7 +239,7 @@ public JsonElement serialize(final BaseCollectionPage src, public BaseCollectionResponse deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { - return CollectionResponseSerializer.deserialize(json, typeOfT, logger); + return CollectionResponseDeserializer.deserialize(json, typeOfT, logger); } }; diff --git a/src/test/java/com/microsoft/graph/models/TestIJsonBackedObject.java b/src/test/java/com/microsoft/graph/models/TestIJsonBackedObject.java index bd0c4a13..b5a80c12 100644 --- a/src/test/java/com/microsoft/graph/models/TestIJsonBackedObject.java +++ b/src/test/java/com/microsoft/graph/models/TestIJsonBackedObject.java @@ -11,14 +11,22 @@ public abstract class TestIJsonBackedObject implements IJsonBackedObject { + AdditionalDataManager additionalDataManager = mock(AdditionalDataManager.class); + + String rawObject; + @Override public void setRawObject(@NotNull ISerializer serializer, @NotNull JsonObject json) { - // Do nothing + this.rawObject = json.toString(); } @Nullable @Override public AdditionalDataManager additionalDataManager() { - return mock(AdditionalDataManager.class); + return additionalDataManager; + } + + public String getRawObject() { + return rawObject; } } diff --git a/src/test/java/com/microsoft/graph/serializer/DefaultSerializerTest.java b/src/test/java/com/microsoft/graph/serializer/DefaultSerializerTest.java new file mode 100644 index 00000000..459e9b14 --- /dev/null +++ b/src/test/java/com/microsoft/graph/serializer/DefaultSerializerTest.java @@ -0,0 +1,36 @@ +package com.microsoft.graph.serializer; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.microsoft.graph.logger.ILogger; +import com.microsoft.graph.models.ReactionStub; +import com.microsoft.graph.models.SubReactionStub1; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +public class DefaultSerializerTest { + + final ILogger logger = mock(ILogger.class); + Gson gson = GsonFactory.getGsonInstance(logger); + DefaultSerializer defaultSerializer = new DefaultSerializer(logger); + + @Test + public void testDeserializationOfObjectWithODataTypeProperty() { + // Given + final String testJsonResponse = + "{\"@odata.type\": \"#microsoft.graph.subReactionStub1\"}"; + + // When + ReactionStub reaction = defaultSerializer.deserializeObject(testJsonResponse, ReactionStub.class); + + // Then + assertTrue(reaction instanceof SubReactionStub1); + assertEquals("{\"@odata.type\":\"#microsoft.graph.subReactionStub1\"}", reaction.getRawObject()); + Mockito.verify(reaction.additionalDataManager()).setAdditionalData(gson.fromJson(testJsonResponse, JsonElement.class).getAsJsonObject()); + } + +} From 94b30f0a7c4710887d5368e7e2f077d82556a297 Mon Sep 17 00:00:00 2001 From: ramsessanchez <63934382+ramsessanchez@users.noreply.github.com> Date: Mon, 26 Jul 2021 15:56:20 -0700 Subject: [PATCH 15/25] Bump target android SDK version to 31 --- android/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 204b2531..1086c94d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -23,13 +23,13 @@ apply plugin: "com.android.library" apply plugin: "com.github.ben-manes.versions" android { - compileSdkVersion 30 + compileSdkVersion 31 defaultConfig { versionCode 1 versionName "1.0" minSdkVersion 26 - targetSdkVersion 30 + targetSdkVersion 31 } buildTypes { From f6cec76236c6e9cc298c6a56e635f27fe4428461 Mon Sep 17 00:00:00 2001 From: ramsessanchez Date: Mon, 2 Aug 2021 12:53:18 -0700 Subject: [PATCH 16/25] update to 2.0.8 from 2.0.7 --- gradle.properties | 2 +- pom.xml | 2 +- .../java/com/microsoft/graph/httpcore/TelemetryHandler.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 907c5c30..884db4ec 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,7 +25,7 @@ mavenGroupId = com.microsoft.graph mavenArtifactId = microsoft-graph-core mavenMajorVersion = 2 mavenMinorVersion = 0 -mavenPatchVersion = 7 +mavenPatchVersion = 8 mavenArtifactSuffix = #These values are used to run functional tests diff --git a/pom.xml b/pom.xml index f2c0d6c4..2367f3c1 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ com.microsoft.graph microsoft-graph-core - 2.0.7 + 2.0.8 pom diff --git a/src/main/java/com/microsoft/graph/httpcore/TelemetryHandler.java b/src/main/java/com/microsoft/graph/httpcore/TelemetryHandler.java index 9e4abf7d..44632a60 100644 --- a/src/main/java/com/microsoft/graph/httpcore/TelemetryHandler.java +++ b/src/main/java/com/microsoft/graph/httpcore/TelemetryHandler.java @@ -24,7 +24,7 @@ public class TelemetryHandler implements Interceptor{ /** * Current SDK version */ - public static final String VERSION = "v2.0.7"; + public static final String VERSION = "v2.0.8"; /** * Verion prefix */ From 123b1bbf5b734b1c46307977f8537bbc87924923 Mon Sep 17 00:00:00 2001 From: ramsessanchez <63934382+ramsessanchez@users.noreply.github.com> Date: Mon, 2 Aug 2021 12:55:55 -0700 Subject: [PATCH 17/25] Bump from version 2.0.7 to 2.0.8 --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 219f6c66..688c3d4a 100644 --- a/readme.md +++ b/readme.md @@ -22,7 +22,7 @@ repositories { dependencies { // Include the sdk as a dependency - implementation 'com.microsoft.graph:microsoft-graph-core:2.0.7' + implementation 'com.microsoft.graph:microsoft-graph-core:2.0.8' // This dependency is only needed if you are using the TokenCrendentialAuthProvider implementation 'com.azure:azure-identity:1.3.1' } @@ -37,7 +37,7 @@ Add the dependency in `dependencies` in pom.xml com.microsoft.graph microsoft-graph-core - 2.0.7 + 2.0.8 com.azure azure-identity From b579e11dfdd9f29f49d169e1c8d96e59487abc99 Mon Sep 17 00:00:00 2001 From: Markus Mertens Date: Wed, 4 Aug 2021 16:25:42 +0200 Subject: [PATCH 18/25] fixed serialization problems for mime messages --- .../com/microsoft/graph/http/CoreHttpProvider.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/graph/http/CoreHttpProvider.java b/src/main/java/com/microsoft/graph/http/CoreHttpProvider.java index 4b53f5bb..3b50842c 100644 --- a/src/main/java/com/microsoft/graph/http/CoreHttpProvider.java +++ b/src/main/java/com/microsoft/graph/http/CoreHttpProvider.java @@ -296,7 +296,15 @@ public Request getHttpRequest(@Nonnull final IHttpRequest request } } else { logger.logDebug("Sending " + serializable.getClass().getName() + " as request body"); - final String serializeObject = serializer.serializeObject(serializable); + + String serializeObject = null; + + if ("text/plain".equals(contenttype) && serializable instanceof String) { + serializeObject = (String)serializable; + } else { + serializeObject = serializer.serializeObject(serializable); + } + if(serializeObject == null) { throw new ClientException("Error during serialization of request body, the result was null", null); } From 161fa3ece4fc66e8f018b8e7271e7a8677e6a443 Mon Sep 17 00:00:00 2001 From: David Dellsperger Date: Wed, 4 Aug 2021 16:00:53 -0500 Subject: [PATCH 19/25] Add support to set a setting to serialize null values using the DefaultSerializer and GSONFactory --- .../graph/serializer/DefaultSerializer.java | 15 ++++++++-- .../graph/serializer/GsonFactory.java | 17 ++++++++++- .../serializer/DefaultSerializerTest.java | 30 +++++++++++++++++++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java b/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java index 3ea6cf36..328799db 100644 --- a/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java +++ b/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java @@ -65,10 +65,21 @@ public class DefaultSerializer implements ISerializer { * @param logger the logger */ public DefaultSerializer(@Nonnull final ILogger logger) { - this.logger = Objects.requireNonNull(logger, "parameter logger cannot be null"); - this.gson = GsonFactory.getGsonInstance(logger); + this(false, logger); } + + /** + * Creates a DefaultSerializer with an option to enable serializing of the null values. + * + * @param serializeNulls the setting of whether or not to serialize the null values in the JSON object + * @param logger the logger + */ + public DefaultSerializer(@Nonnull final boolean serializeNulls, @Nonnull final ILogger logger) { + this.logger = Objects.requireNonNull(logger, "parameter logger cannot be null"); + this.gson = GsonFactory.getGsonInstance(serializeNulls, logger); + } + @Override @Nullable public T deserializeObject(@Nonnull final String inputString, @Nonnull final Class clazz, @Nullable final Map> responseHeaders) { diff --git a/src/main/java/com/microsoft/graph/serializer/GsonFactory.java b/src/main/java/com/microsoft/graph/serializer/GsonFactory.java index 1b24e1c0..841f8f37 100644 --- a/src/main/java/com/microsoft/graph/serializer/GsonFactory.java +++ b/src/main/java/com/microsoft/graph/serializer/GsonFactory.java @@ -69,6 +69,17 @@ private GsonFactory() { * @return the new instance */ public static Gson getGsonInstance(final ILogger logger) { + return getGsonInstance(false, logger); + } + + /** + * Creates an instance of GSON + * + * @param serializeNulls the setting of whether or not to serialize the null values in the JSON object + * @param logger the logger + * @return the new instance + */ + public static Gson getGsonInstance(final boolean serializeNulls, final ILogger logger) { final JsonSerializer calendarJsonSerializer = new JsonSerializer() { @Override @@ -328,7 +339,11 @@ public Float deserialize(final JsonElement json, } }; - return new GsonBuilder() + GsonBuilder builder = new GsonBuilder(); + if(serializeNulls) { + builder.serializeNulls(); + } + return builder .excludeFieldsWithoutExposeAnnotation() .registerTypeAdapter(Boolean.class, booleanJsonDeserializer) .registerTypeAdapter(String.class, stringJsonDeserializer) diff --git a/src/test/java/com/microsoft/graph/serializer/DefaultSerializerTest.java b/src/test/java/com/microsoft/graph/serializer/DefaultSerializerTest.java index 459e9b14..53301505 100644 --- a/src/test/java/com/microsoft/graph/serializer/DefaultSerializerTest.java +++ b/src/test/java/com/microsoft/graph/serializer/DefaultSerializerTest.java @@ -3,6 +3,7 @@ import com.google.gson.Gson; import com.google.gson.JsonElement; import com.microsoft.graph.logger.ILogger; +import com.microsoft.graph.models.MessageStub; import com.microsoft.graph.models.ReactionStub; import com.microsoft.graph.models.SubReactionStub1; import org.junit.jupiter.api.Test; @@ -17,6 +18,7 @@ public class DefaultSerializerTest { final ILogger logger = mock(ILogger.class); Gson gson = GsonFactory.getGsonInstance(logger); DefaultSerializer defaultSerializer = new DefaultSerializer(logger); + DefaultSerializer defaultNullSerializer = new DefaultSerializer(true, logger); @Test public void testDeserializationOfObjectWithODataTypeProperty() { @@ -33,4 +35,32 @@ public void testDeserializationOfObjectWithODataTypeProperty() { Mockito.verify(reaction.additionalDataManager()).setAdditionalData(gson.fromJson(testJsonResponse, JsonElement.class).getAsJsonObject()); } + @Test + public void testDefaultSerializerDoesNotIncludeNullValuesByDefault() { + // Given + final String testJsonResponse = + "{\"@odata.type\": \"#microsoft.graph.messageStub\", \"body\": null}"; + + // When + DefaultSerializer nonNullSerializer = new DefaultSerializer(logger); + MessageStub message = nonNullSerializer.deserializeObject(testJsonResponse, MessageStub.class); + + // Then + assertEquals("{}", nonNullSerializer.serializeObject(message)); + } + + @Test + public void testDefaultNullSerializerDoesIncludeNullValues() { + // Given + final String testJsonResponse = + "{\"@odata.type\": \"#microsoft.graph.messageStub\",\"body\":null}"; + + // When + DefaultSerializer nullSerializer = new DefaultSerializer(true, logger); + MessageStub message = nullSerializer.deserializeObject(testJsonResponse, MessageStub.class); + + // Then + assertEquals("{\"body\":null,\"reaction\":null}", nullSerializer.serializeObject(message)); + } + } From 2444ecaae4e34dd752c4e6f72a0494b0ae308ec4 Mon Sep 17 00:00:00 2001 From: Markus Mertens Date: Thu, 5 Aug 2021 08:08:53 +0200 Subject: [PATCH 20/25] added unit test for "text/plain" request bodies --- .../graph/http/CoreHttpProviderTests.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java b/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java index 001d9a08..84037798 100644 --- a/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java +++ b/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java @@ -20,6 +20,7 @@ import com.microsoft.graph.serializer.DefaultSerializer; import com.microsoft.graph.serializer.ISerializer; +import okio.Buffer; import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; @@ -39,6 +40,7 @@ import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; +import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -49,6 +51,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class CoreHttpProviderTests { @@ -288,4 +292,34 @@ public void getHttpRequestSetsRetryOrRedirectOptionsOnNonDefaultValues() throws assertNotNull(retryOptions); } + + @Test + public void getHttpRequestWithTextPlainBodyDoesNotSerializeAsJson() throws IOException { + final IHttpRequest absRequest = mock(IHttpRequest.class); + when(absRequest.getRequestUrl()).thenReturn(new URL("https://graph.microsoft.com/v1.0/me")); + when(absRequest.getHttpMethod()).thenReturn(HttpMethod.POST); + final ISerializer serializer = mock(ISerializer.class); + final ILogger logger = mock(ILogger.class); + + mProvider = new CoreHttpProvider(serializer, + logger, + new OkHttpClient.Builder().build()); + + // GIVEN: A "text/plain" request body + HeaderOption option = new HeaderOption("Content-Type", "text/plain"); + when(absRequest.getHeaders()).thenReturn(Arrays.asList(option)); + String expectedBody = "Plain String Body"; + + //WHEN: getHttpRequest is called + Request request = mProvider.getHttpRequest(absRequest, String.class, expectedBody); + + // THEN: The serializer must not be called + verify(serializer, never()).serializeObject(Mockito.any()); + + // AND: We expect the request body to contain the plain String, not serialized as Json + Buffer buffer = new Buffer(); + request.body().writeTo(buffer); + String actualRequestBody = buffer.readUtf8(); + assertEquals(expectedBody, actualRequestBody); + } } From 8debce9318f2f57944331d8cccde3f41351f2253 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 5 Aug 2021 07:54:47 -0400 Subject: [PATCH 21/25] - fixes a linting issue for null check --- .../graph/http/CoreHttpProviderTests.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java b/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java index 84037798..ef503548 100644 --- a/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java +++ b/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java @@ -40,6 +40,7 @@ import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; +import okhttp3.RequestBody; import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -306,20 +307,22 @@ public void getHttpRequestWithTextPlainBodyDoesNotSerializeAsJson() throws IOExc new OkHttpClient.Builder().build()); // GIVEN: A "text/plain" request body - HeaderOption option = new HeaderOption("Content-Type", "text/plain"); + final HeaderOption option = new HeaderOption("Content-Type", "text/plain"); when(absRequest.getHeaders()).thenReturn(Arrays.asList(option)); - String expectedBody = "Plain String Body"; + final String expectedBody = "Plain String Body"; //WHEN: getHttpRequest is called - Request request = mProvider.getHttpRequest(absRequest, String.class, expectedBody); + final Request request = mProvider.getHttpRequest(absRequest, String.class, expectedBody); // THEN: The serializer must not be called verify(serializer, never()).serializeObject(Mockito.any()); // AND: We expect the request body to contain the plain String, not serialized as Json - Buffer buffer = new Buffer(); - request.body().writeTo(buffer); - String actualRequestBody = buffer.readUtf8(); + final Buffer buffer = new Buffer(); + final RequestBody requestBody = request.body(); + assertNotNull(requestBody); + requestBody.writeTo(buffer); + final String actualRequestBody = buffer.readUtf8(); assertEquals(expectedBody, actualRequestBody); } } From bb3f95407557fed7a63a3c46d7f46991b625eb71 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 5 Aug 2021 07:58:25 -0400 Subject: [PATCH 22/25] - adds missing javadoc comments --- .../microsoft/graph/serializer/DerivedClassIdentifier.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/graph/serializer/DerivedClassIdentifier.java b/src/main/java/com/microsoft/graph/serializer/DerivedClassIdentifier.java index 6fbe1a4b..e01346a0 100644 --- a/src/main/java/com/microsoft/graph/serializer/DerivedClassIdentifier.java +++ b/src/main/java/com/microsoft/graph/serializer/DerivedClassIdentifier.java @@ -8,12 +8,16 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +/** This class provides methods to get the derived class corresponding to the OData type when deserializing payloads. */ public class DerivedClassIdentifier { private final static String ODATA_TYPE_KEY = "@odata.type"; private final ILogger logger; - + /** + * Creates a new instance of the dereived class identifier. + * @param logger The logger to use. + */ public DerivedClassIdentifier(@Nonnull ILogger logger) { this.logger = Objects.requireNonNull(logger, "logger parameter cannot be null");; } From d7725678e4789d8305cf4243ab3bd401cc09b061 Mon Sep 17 00:00:00 2001 From: David Dellsperger Date: Thu, 5 Aug 2021 09:20:53 -0500 Subject: [PATCH 23/25] Updated based on PR feedback --- .../graph/serializer/DefaultSerializer.java | 8 ++++---- .../microsoft/graph/serializer/GsonFactory.java | 16 ++++++++++------ .../graph/serializer/DefaultSerializerTest.java | 13 +++++++------ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java b/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java index 328799db..5b510482 100644 --- a/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java +++ b/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java @@ -65,19 +65,19 @@ public class DefaultSerializer implements ISerializer { * @param logger the logger */ public DefaultSerializer(@Nonnull final ILogger logger) { - this(false, logger); + this(logger, false); } /** * Creates a DefaultSerializer with an option to enable serializing of the null values. * - * @param serializeNulls the setting of whether or not to serialize the null values in the JSON object * @param logger the logger + * @param serializeNulls the setting of whether or not to serialize the null values in the JSON object */ - public DefaultSerializer(@Nonnull final boolean serializeNulls, @Nonnull final ILogger logger) { + public DefaultSerializer(@Nonnull final ILogger logger, @Nonnull final boolean serializeNulls) { this.logger = Objects.requireNonNull(logger, "parameter logger cannot be null"); - this.gson = GsonFactory.getGsonInstance(serializeNulls, logger); + this.gson = GsonFactory.getGsonInstance(logger, serializeNulls); } @Override diff --git a/src/main/java/com/microsoft/graph/serializer/GsonFactory.java b/src/main/java/com/microsoft/graph/serializer/GsonFactory.java index 841f8f37..645766f5 100644 --- a/src/main/java/com/microsoft/graph/serializer/GsonFactory.java +++ b/src/main/java/com/microsoft/graph/serializer/GsonFactory.java @@ -44,8 +44,10 @@ import java.util.Arrays; import java.util.EnumSet; import java.util.GregorianCalendar; +import java.util.Objects; import java.util.UUID; +import javax.annotation.Nonnull; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.Duration; @@ -68,19 +70,21 @@ private GsonFactory() { * @param logger the logger * @return the new instance */ - public static Gson getGsonInstance(final ILogger logger) { - return getGsonInstance(false, logger); + @Nonnull + public static Gson getGsonInstance(@Nonnull final ILogger logger) { + return getGsonInstance(logger, false); } /** * Creates an instance of GSON * - * @param serializeNulls the setting of whether or not to serialize the null values in the JSON object * @param logger the logger + * @param serializeNulls the setting of whether or not to serialize the null values in the JSON object * @return the new instance */ - public static Gson getGsonInstance(final boolean serializeNulls, final ILogger logger) { - + @Nonnull + public static Gson getGsonInstance(@Nonnull final ILogger logger, final boolean serializeNulls) { + Objects.requireNonNull(logger, "parameter logger cannot be null"); final JsonSerializer calendarJsonSerializer = new JsonSerializer() { @Override public JsonElement serialize(final OffsetDateTime src, @@ -340,7 +344,7 @@ public Float deserialize(final JsonElement json, }; GsonBuilder builder = new GsonBuilder(); - if(serializeNulls) { + if (serializeNulls) { builder.serializeNulls(); } return builder diff --git a/src/test/java/com/microsoft/graph/serializer/DefaultSerializerTest.java b/src/test/java/com/microsoft/graph/serializer/DefaultSerializerTest.java index 53301505..de269d61 100644 --- a/src/test/java/com/microsoft/graph/serializer/DefaultSerializerTest.java +++ b/src/test/java/com/microsoft/graph/serializer/DefaultSerializerTest.java @@ -10,6 +10,7 @@ import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; @@ -18,7 +19,6 @@ public class DefaultSerializerTest { final ILogger logger = mock(ILogger.class); Gson gson = GsonFactory.getGsonInstance(logger); DefaultSerializer defaultSerializer = new DefaultSerializer(logger); - DefaultSerializer defaultNullSerializer = new DefaultSerializer(true, logger); @Test public void testDeserializationOfObjectWithODataTypeProperty() { @@ -42,11 +42,11 @@ public void testDefaultSerializerDoesNotIncludeNullValuesByDefault() { "{\"@odata.type\": \"#microsoft.graph.messageStub\", \"body\": null}"; // When - DefaultSerializer nonNullSerializer = new DefaultSerializer(logger); - MessageStub message = nonNullSerializer.deserializeObject(testJsonResponse, MessageStub.class); + final MessageStub message = defaultSerializer.deserializeObject(testJsonResponse, MessageStub.class); // Then - assertEquals("{}", nonNullSerializer.serializeObject(message)); + assertNotNull(message); + assertEquals("{}", defaultSerializer.serializeObject(message)); } @Test @@ -56,10 +56,11 @@ public void testDefaultNullSerializerDoesIncludeNullValues() { "{\"@odata.type\": \"#microsoft.graph.messageStub\",\"body\":null}"; // When - DefaultSerializer nullSerializer = new DefaultSerializer(true, logger); - MessageStub message = nullSerializer.deserializeObject(testJsonResponse, MessageStub.class); + final DefaultSerializer nullSerializer = new DefaultSerializer(logger, true); + final MessageStub message = nullSerializer.deserializeObject(testJsonResponse, MessageStub.class); // Then + assertNotNull(message); assertEquals("{\"body\":null,\"reaction\":null}", nullSerializer.serializeObject(message)); } From 2af4aaeef47321759e92a0cc40d6e09a6651b088 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 6 Aug 2021 07:44:50 -0400 Subject: [PATCH 24/25] - code linting --- .../graph/http/CoreHttpProviderTests.java | 26 +++++++++---------- .../serializer/DefaultSerializerTest.java | 8 +++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java b/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java index ef503548..43b8ebe7 100644 --- a/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java +++ b/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java @@ -56,13 +56,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public class CoreHttpProviderTests { +class CoreHttpProviderTests { private CoreHttpProvider mProvider; private Gson GSON = new GsonBuilder().create(); @Test - public void testErrorResponse() throws Exception { + void testErrorResponse() throws Exception { final GraphErrorCodes expectedErrorCode = GraphErrorCodes.INVALID_REQUEST; final String expectedMessage = "Test error!"; final GraphErrorResponse toSerialize = new GraphErrorResponse(); @@ -85,7 +85,7 @@ public void testErrorResponse() throws Exception { } @Test - public void testVerboseErrorResponse() throws Exception { + void testVerboseErrorResponse() throws Exception { final GraphErrorCodes expectedErrorCode = GraphErrorCodes.INVALID_REQUEST; final String expectedMessage = "Test error!"; final GraphErrorResponse toSerialize = new GraphErrorResponse(); @@ -119,25 +119,25 @@ public void testVerboseErrorResponse() throws Exception { } @Test - public void testHasHeaderReturnsTrue() { + void testHasHeaderReturnsTrue() { HeaderOption h = new HeaderOption("name", "value"); assertTrue(CoreHttpProvider.hasHeader(Arrays.asList(h), "name")); } @Test - public void testHasHeaderReturnsTrueWhenDifferentCase() { + void testHasHeaderReturnsTrueWhenDifferentCase() { HeaderOption h = new HeaderOption("name", "value"); assertTrue(CoreHttpProvider.hasHeader(Arrays.asList(h), "NAME")); } @Test - public void testHasHeaderReturnsFalse() { + void testHasHeaderReturnsFalse() { HeaderOption h = new HeaderOption("name", "value"); assertFalse(CoreHttpProvider.hasHeader(Arrays.asList(h), "blah")); } @Test - public void testStreamToStringReturnsData() { + void testStreamToStringReturnsData() { String data = GSON.toJson(Maps.newHashMap( ImmutableMap.builder() .put("key", "value") @@ -149,14 +149,14 @@ public void testStreamToStringReturnsData() { } @Test - public void testStreamToStringReturnsEmpty() { + void testStreamToStringReturnsEmpty() { final InputStream inputStream = new ByteArrayInputStream(new byte[0]); String convertedData = CoreHttpProvider.streamToString(inputStream); assertEquals("", convertedData); } @Test - public void emptyPostContentTypeIsNotReset() { + void emptyPostContentTypeIsNotReset() { final String contentTypeValue = "application/json"; final HeaderOption ctype = new HeaderOption("Content-Type", contentTypeValue); final ArrayList