diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 10a1e9e17..88bfd2d01 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 diff --git a/android/build.gradle b/android/build.gradle index 204b25314..1086c94d3 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 { diff --git a/gradle.properties b/gradle.properties index 907c5c302..884db4ece 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/gradle/dependencies.gradle b/gradle/dependencies.gradle index 38c853013..38372fff6 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -2,12 +2,12 @@ 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' 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 diff --git a/pom.xml b/pom.xml index f0e1d0f8c..2367f3c17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ com.microsoft.graph microsoft-graph-core - 2.0.2 + 2.0.8 pom @@ -35,7 +35,7 @@ com.azure azure-core - 1.17.0 + 1.18.0 org.junit.jupiter @@ -46,8 +46,8 @@ org.mockito mockito-inline - 3.11.1 + 3.11.2 test - \ No newline at end of file + diff --git a/readme.md b/readme.md index 219f6c662..688c3d4ae 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 diff --git a/src/main/java/com/microsoft/graph/http/CoreHttpProvider.java b/src/main/java/com/microsoft/graph/http/CoreHttpProvider.java index 4b53f5bbe..3b50842c5 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); } diff --git a/src/main/java/com/microsoft/graph/httpcore/TelemetryHandler.java b/src/main/java/com/microsoft/graph/httpcore/TelemetryHandler.java index 9e4abf7d2..44632a60c 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 */ diff --git a/src/main/java/com/microsoft/graph/serializer/CollectionPageSerializer.java b/src/main/java/com/microsoft/graph/serializer/CollectionPageSerializer.java index dd305b532..424503af1 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 88% rename from src/main/java/com/microsoft/graph/serializer/CollectionResponseSerializer.java rename to src/main/java/com/microsoft/graph/serializer/CollectionResponseDeserializer.java index 2f63fb66c..5b9c5c5a1 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 * @@ -86,15 +86,7 @@ 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); - 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 2d9d5d2e8..99c31906e 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; @@ -48,17 +45,19 @@ * 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; /** * Creates a DefaultSerializer @@ -66,10 +65,26 @@ 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(logger, false); } + + /** + * Creates a DefaultSerializer with an option to enable serializing of the null values. + * + * Serializing of null values can have side effects on the service behavior. + * Sending null values in a PATCH request might reset existing values on the service side. + * Sending null values in a POST request might prevent the service from assigning default values to the properties. + * It is not recommended to send null values to the service in general and this setting should only be used when serializing information for a local store. + * + * @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 ILogger logger, @Nonnull final boolean serializeNulls) { + this.logger = Objects.requireNonNull(logger, "parameter logger cannot be null"); + this.gson = GsonFactory.getGsonInstance(logger, serializeNulls); + } + @Override @Nullable public T deserializeObject(@Nonnull final String inputString, @Nonnull final Class clazz, @Nullable final Map> responseHeaders) { @@ -104,16 +119,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 = this.getDerivedClass(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); @@ -123,9 +129,9 @@ 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; + return jsonObject; } else { logger.logDebug("Deserializing a non-IJsonBackedObject type " + clazz.getSimpleName()); return jsonObject; @@ -304,52 +310,12 @@ 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()); } } } - 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 000000000..e01346a02 --- /dev/null +++ b/src/main/java/com/microsoft/graph/serializer/DerivedClassIdentifier.java @@ -0,0 +1,64 @@ +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; + +/** 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");; + } + + /** + * 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 9c439c65d..849e8750b 100644 --- a/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java +++ b/src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java @@ -22,23 +22,23 @@ 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.TypeAdapter; import com.google.gson.TypeAdapterFactory; +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.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 @@ -67,14 +67,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 */ @@ -89,10 +89,21 @@ 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); + return new EnumTypeAdapter<>(rawType, logger); } else if (rawType == Void.class) { 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 (!(delegatedAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) { + return null; + } + + return (TypeAdapter) new ODataTypeParametrizedIJsonBackedTypedAdapter(this, gson, delegatedAdapter, (TypeToken) type, logger); } else { return null; diff --git a/src/main/java/com/microsoft/graph/serializer/GsonFactory.java b/src/main/java/com/microsoft/graph/serializer/GsonFactory.java index 4a040cc99..4b305e5ae 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,8 +70,26 @@ private GsonFactory() { * @param logger the logger * @return the new instance */ - public static Gson getGsonInstance(final ILogger logger) { + @Nonnull + public static Gson getGsonInstance(@Nonnull final ILogger logger) { + return getGsonInstance(logger, false); + } + /** + * Creates an instance of GSON + * + * Serializing of null values can have side effects on the service behavior. + * Sending null values in a PATCH request might reset existing values on the service side. + * Sending null values in a POST request might prevent the service from assigning default values to the properties. + * It is not recommended to send null values to the service in general and this setting should only be used when serializing information for a local store. + * + * @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 + */ + @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, @@ -239,7 +259,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); } }; @@ -328,7 +348,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/main/java/com/microsoft/graph/serializer/ODataTypeParametrizedIJsonBackedTypedAdapter.java b/src/main/java/com/microsoft/graph/serializer/ODataTypeParametrizedIJsonBackedTypedAdapter.java new file mode 100644 index 000000000..4b2620d5b --- /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); + } +} diff --git a/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java b/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java index 001d9a080..43b8ebe76 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,8 @@ import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; +import okhttp3.RequestBody; +import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -49,15 +52,17 @@ 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 { +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(); @@ -80,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(); @@ -114,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") @@ -144,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