From 0c6f373e138ebc8229345ce69b6ec192e5072e49 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 9 Mar 2021 14:50:28 -0500 Subject: [PATCH 1/3] - fixes a bug where native EDM return types would not be deserialized properly --- .../serializer/EdmNativeTypeSerializer.java | 60 +++++++++++++++ .../graph/serializer/GsonFactory.java | 72 ++++++++++++++++++ .../EdmNativeTypeSerializerTests.java | 76 +++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 src/main/java/com/microsoft/graph/serializer/EdmNativeTypeSerializer.java create mode 100644 src/test/java/com/microsoft/graph/serializer/EdmNativeTypeSerializerTests.java diff --git a/src/main/java/com/microsoft/graph/serializer/EdmNativeTypeSerializer.java b/src/main/java/com/microsoft/graph/serializer/EdmNativeTypeSerializer.java new file mode 100644 index 000000000..a8b50075b --- /dev/null +++ b/src/main/java/com/microsoft/graph/serializer/EdmNativeTypeSerializer.java @@ -0,0 +1,60 @@ +package com.microsoft.graph.serializer; + +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.util.UUID; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.microsoft.graph.logger.ILogger; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** Deserializer for native EDM types from the service. Used for actions and functions that return native types for example. */ +public class EdmNativeTypeSerializer { + /** + * Deserializes native EDM types from the service. Used for actions and functions that return native types for example. + * @param Expected return type. + * @param json json to deserialize. + * @param type class of the expected return type. + * @param logger logger to use. + * @return the deserialized type or null. + */ + @Nullable + public static T deserialize(@Nonnull final JsonElement json, @Nonnull final Class type, @Nonnull final ILogger logger) { + if (json == null || type == null) { + return null; + } else if(json.isJsonPrimitive()) { + return getPrimitiveValue(json, type); + } else if(json.isJsonObject()) { + final JsonElement element = json.getAsJsonObject().get("@odata.null"); + if(element != null && element.isJsonPrimitive()) { + return getPrimitiveValue(element, type); + } else { + return null; + } + } + return null; + } + @SuppressWarnings("unchecked") + private static T getPrimitiveValue(final JsonElement json, final Class type) { + if(type == Boolean.class) { + return (T) Boolean.valueOf(json.getAsBoolean()); + } else if(type == String.class) { + return (T)json.getAsString(); + } else if(type == Integer.class) { + return (T) Integer.valueOf(json.getAsInt()); + } else if(type == UUID.class) { + return (T) UUID.fromString(json.getAsString()); + } else if(type == Long.class) { + return (T) Long.valueOf(json.getAsLong()); + } else if (type == Float.class) { + return (T) Float.valueOf(json.getAsFloat()); + } else if (type == BigDecimal.class) { + return (T) json.getAsBigDecimal(); + } 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 e51b998cf..d4da62625 100644 --- a/src/main/java/com/microsoft/graph/serializer/GsonFactory.java +++ b/src/main/java/com/microsoft/graph/serializer/GsonFactory.java @@ -38,11 +38,13 @@ import com.microsoft.graph.core.TimeOfDay; import java.lang.reflect.Type; +import java.math.BigDecimal; import java.text.ParseException; import java.time.OffsetDateTime; import java.util.Arrays; import java.util.EnumSet; import java.util.GregorianCalendar; +import java.util.UUID; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.Duration; @@ -254,8 +256,78 @@ public TimeOfDay deserialize(final JsonElement json, } }; + final JsonDeserializer booleanJsonDeserializer = new JsonDeserializer() { + @Override + public Boolean deserialize(final JsonElement json, + final Type typeOfT, + final JsonDeserializationContext context) throws JsonParseException { + return EdmNativeTypeSerializer.deserialize(json, Boolean.class, logger); + } + }; + + final JsonDeserializer stringJsonDeserializer = new JsonDeserializer() { + @Override + public String deserialize(final JsonElement json, + final Type typeOfT, + final JsonDeserializationContext context) throws JsonParseException { + return EdmNativeTypeSerializer.deserialize(json, String.class, logger); + } + }; + + final JsonDeserializer bigDecimalJsonDeserializer = new JsonDeserializer() { + @Override + public BigDecimal deserialize(final JsonElement json, + final Type typeOfT, + final JsonDeserializationContext context) throws JsonParseException { + return EdmNativeTypeSerializer.deserialize(json, BigDecimal.class, logger); + } + }; + + final JsonDeserializer integerJsonDeserializer = new JsonDeserializer() { + @Override + public Integer deserialize(final JsonElement json, + final Type typeOfT, + final JsonDeserializationContext context) throws JsonParseException { + return EdmNativeTypeSerializer.deserialize(json, Integer.class, logger); + } + }; + + final JsonDeserializer longJsonDeserializer = new JsonDeserializer() { + @Override + public Long deserialize(final JsonElement json, + final Type typeOfT, + final JsonDeserializationContext context) throws JsonParseException { + return EdmNativeTypeSerializer.deserialize(json, Long.class, logger); + } + }; + + final JsonDeserializer uuidJsonDeserializer = new JsonDeserializer() { + @Override + public UUID deserialize(final JsonElement json, + final Type typeOfT, + final JsonDeserializationContext context) throws JsonParseException { + return EdmNativeTypeSerializer.deserialize(json, UUID.class, logger); + } + }; + + final JsonDeserializer floatJsonDeserializer = new JsonDeserializer() { + @Override + public Float deserialize(final JsonElement json, + final Type typeOfT, + final JsonDeserializationContext context) throws JsonParseException { + return EdmNativeTypeSerializer.deserialize(json, Float.class, logger); + } + }; + return new GsonBuilder() .excludeFieldsWithoutExposeAnnotation() + .registerTypeAdapter(Boolean.class, booleanJsonDeserializer) + .registerTypeAdapter(String.class, stringJsonDeserializer) + .registerTypeAdapter(Float.class, floatJsonDeserializer) + .registerTypeAdapter(Integer.class, integerJsonDeserializer) + .registerTypeAdapter(BigDecimal.class, bigDecimalJsonDeserializer) + .registerTypeAdapter(UUID.class, uuidJsonDeserializer) + .registerTypeAdapter(Long.class, longJsonDeserializer) .registerTypeAdapter(OffsetDateTime.class, calendarJsonSerializer) .registerTypeAdapter(OffsetDateTime.class, calendarJsonDeserializer) .registerTypeAdapter(GregorianCalendar.class, calendarJsonSerializer) diff --git a/src/test/java/com/microsoft/graph/serializer/EdmNativeTypeSerializerTests.java b/src/test/java/com/microsoft/graph/serializer/EdmNativeTypeSerializerTests.java new file mode 100644 index 000000000..952fe422a --- /dev/null +++ b/src/test/java/com/microsoft/graph/serializer/EdmNativeTypeSerializerTests.java @@ -0,0 +1,76 @@ +package com.microsoft.graph.serializer; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigDecimal; +import java.util.UUID; + +import com.microsoft.graph.logger.DefaultLogger; + +import org.junit.jupiter.api.Test; + +public class EdmNativeTypeSerializerTests { + @Test + public void testBoolean() throws Exception { + final DefaultSerializer serializer = new DefaultSerializer(new DefaultLogger()); + + final String source = "{\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#Edm.Null\",\"@odata.null\":true}"; + final Boolean result = serializer.deserializeObject(source, Boolean.class); + + assertEquals(Boolean.valueOf(true), result); + } + @Test + public void testInteger() throws Exception { + final DefaultSerializer serializer = new DefaultSerializer(new DefaultLogger()); + + final String source = "{\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#Edm.Null\",\"@odata.null\":12}"; + final Integer result = serializer.deserializeObject(source, Integer.class); + + assertEquals(Integer.valueOf(12), result); + } + @Test + public void testString() throws Exception { + final DefaultSerializer serializer = new DefaultSerializer(new DefaultLogger()); + + final String source = "{\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#Edm.Null\",\"@odata.null\":\"toto\"}"; + final String result = serializer.deserializeObject(source, String.class); + + assertEquals("toto", result); + } + @Test + public void testFloat() throws Exception { + final DefaultSerializer serializer = new DefaultSerializer(new DefaultLogger()); + + final String source = "{\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#Edm.Null\",\"@odata.null\":12.5}"; + final Float result = serializer.deserializeObject(source, Float.class); + + assertEquals(Float.valueOf("12.5"), result); + } + @Test + public void testLong() throws Exception { + final DefaultSerializer serializer = new DefaultSerializer(new DefaultLogger()); + + final String source = "{\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#Edm.Null\",\"@odata.null\":12}"; + final Long result = serializer.deserializeObject(source, Long.class); + + assertEquals(Long.valueOf(12), result); + } + @Test + public void testBigDecimal() throws Exception { + final DefaultSerializer serializer = new DefaultSerializer(new DefaultLogger()); + + final String source = "{\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#Edm.Null\",\"@odata.null\":12}"; + final BigDecimal result = serializer.deserializeObject(source, BigDecimal.class); + + assertEquals(BigDecimal.valueOf(12), result); + } + @Test + public void testUUID() throws Exception { + final DefaultSerializer serializer = new DefaultSerializer(new DefaultLogger()); + + final String source = "{\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#Edm.Null\",\"@odata.null\":\"0E6558C3-9640-4385-860A-2A894AC5C246\"}"; + final UUID result = serializer.deserializeObject(source, UUID.class); + + assertEquals(UUID.fromString("0E6558C3-9640-4385-860A-2A894AC5C246"), result); + } +} From b9b566c72ebef166d87c8a2cb19aac05be02b929 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 9 Mar 2021 14:50:58 -0500 Subject: [PATCH 2/3] - fixes a bug where the content type for requests with empty bodies would be wrongfully defaulted --- .../graph/http/CoreHttpProvider.java | 12 +++--- .../graph/http/CoreHttpProviderTests.java | 43 +++++++++++++++++-- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/microsoft/graph/http/CoreHttpProvider.java b/src/main/java/com/microsoft/graph/http/CoreHttpProvider.java index 59d6b04c4..3fb14b462 100644 --- a/src/main/java/com/microsoft/graph/http/CoreHttpProvider.java +++ b/src/main/java/com/microsoft/graph/http/CoreHttpProvider.java @@ -268,11 +268,7 @@ public Request getHttpRequest(@Nonnull final IHttpRequest request // This ensures that the Content-Length header is properly set if (request.getHttpMethod() == HttpMethod.POST) { bytesToWrite = new byte[0]; - if(contenttype == null) { - contenttype = BINARY_CONTENT_TYPE; - } - } - else { + } else { bytesToWrite = null; } } else if (serializable instanceof byte[]) { @@ -329,7 +325,11 @@ public void writeTo(BufferedSink sink) throws IOException { @Override public MediaType contentType() { - return MediaType.parse(mediaContentType); + if(mediaContentType == null || mediaContentType.isEmpty()) { + return null; + } else { + return MediaType.parse(mediaContentType); + } } }; } diff --git a/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java b/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java index 5e9309cfc..2a3c931e0 100644 --- a/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java +++ b/src/test/java/com/microsoft/graph/http/CoreHttpProviderTests.java @@ -8,9 +8,11 @@ import com.google.gson.JsonPrimitive; import com.microsoft.graph.core.GraphErrorCodes; +import com.microsoft.graph.core.IBaseClient; import com.microsoft.graph.logger.ILogger; import com.microsoft.graph.logger.LoggerLevel; import com.microsoft.graph.options.HeaderOption; +import com.microsoft.graph.options.Option; import com.microsoft.graph.serializer.DefaultSerializer; import com.microsoft.graph.serializer.ISerializer; @@ -21,7 +23,9 @@ import java.io.InputStream; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import okhttp3.Call; import okhttp3.MediaType; @@ -33,6 +37,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; @@ -63,14 +68,14 @@ public void testErrorResponse() throws Exception { mProvider.send(mRequest, Object.class, null); fail("Expected exception in previous statement"); } catch (final GraphServiceException e) { - assertTrue(e.getMessage().indexOf("truncated") > 0); + assertTrue(e.getMessage().indexOf("truncated") > 0); assertEquals(expectedMessage, e.getServiceError().message); } } @Test public void testVerboseErrorResponse() throws Exception { - final GraphErrorCodes expectedErrorCode = GraphErrorCodes.INVALID_REQUEST; + final GraphErrorCodes expectedErrorCode = GraphErrorCodes.INVALID_REQUEST; final String expectedMessage = "Test error!"; final GraphErrorResponse toSerialize = new GraphErrorResponse(); toSerialize.error = new GraphError(); @@ -97,8 +102,8 @@ public void testVerboseErrorResponse() throws Exception { mProvider.send(mRequest, Object.class, null); fail("Expected exception in previous statement"); } catch (final GraphServiceException e) { - assertFalse(e.getMessage().indexOf("truncated") > 0); - assertTrue(e.getMessage().indexOf("The raw request was invalid") < 0); + assertFalse(e.getMessage().indexOf("truncated") > 0); + assertTrue(e.getMessage().indexOf("The raw request was invalid") < 0); } } @@ -139,6 +144,36 @@ public void testStreamToStringReturnsEmpty() { String convertedData = CoreHttpProvider.streamToString(inputStream); assertEquals("", convertedData); } + @Test + public void emptyPostContentTypeIsNotReset() { + final String contentTypeValue = "application/json"; + final HeaderOption ctype = new HeaderOption("Content-Type", contentTypeValue); + final ArrayList