diff --git a/src/main/java/com/microsoft/graph/serializer/AdditionalDataManager.java b/src/main/java/com/microsoft/graph/serializer/AdditionalDataManager.java index 456721cdf..24489c638 100644 --- a/src/main/java/com/microsoft/graph/serializer/AdditionalDataManager.java +++ b/src/main/java/com/microsoft/graph/serializer/AdditionalDataManager.java @@ -1,16 +1,16 @@ // ------------------------------------------------------------------------------ // Copyright (c) 2017 Microsoft Corporation -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sub-license, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -38,12 +38,12 @@ public class AdditionalDataManager extends HashMap { private static final long serialVersionUID = 8641634955796941429L; - + private final transient IJsonBackedObject jsonBackedObject; /** * Instanciates a new additional data manager from the json backed object - * + * * @param jsonBackedObject the object to read values from */ public AdditionalDataManager(@Nullable final IJsonBackedObject jsonBackedObject) { @@ -56,15 +56,15 @@ public AdditionalDataManager(@Nullable final IJsonBackedObject jsonBackedObject) * * @param json the raw JSON to set as additionalData */ - final void setAdditionalData(JsonObject json) { + final void setAdditionalData(final JsonObject json) { // Get the names of all the fields on this object's hierarchy - Set objectFields = getFields(); + final Set objectFields = getFields(); // Get the keys on this JSON - Set jsonKeys = getJsonKeys(json); + final Set jsonKeys = getJsonKeys(json); // Get all keys present in JSON and *NOT* present in fields - Set additionalDataKeys = new HashSet<>(jsonKeys); + final Set additionalDataKeys = new HashSet<>(jsonKeys); additionalDataKeys.removeAll(objectFields); // set the additionalData @@ -73,9 +73,9 @@ final void setAdditionalData(JsonObject json) { } } - private Set getJsonKeys(JsonObject json) { - Set keys = new HashSet<>(); - Set> entries = json.entrySet(); + private Set getJsonKeys(final JsonObject json) { + final Set keys = new HashSet<>(); + final Set> entries = json.entrySet(); for (Map.Entry entry : entries) { keys.add(entry.getKey()); } @@ -83,13 +83,15 @@ private Set getJsonKeys(JsonObject json) { } private Set getFields() { - Field[] fields = jsonBackedObject.getClass().getFields(); - Set serializingFields = new HashSet<>(); - for (Field field : fields) { - SerializedName serializedName; - if (null != (serializedName = field.getAnnotation(SerializedName.class)) - && null != field.getAnnotation(Expose.class)) { - serializingFields.add(serializedName.value()); + final Set serializingFields = new HashSet<>(); + if(jsonBackedObject != null ) { + final Field[] fields = jsonBackedObject.getClass().getFields(); + for (Field field : fields) { + final SerializedName serializedName = field.getAnnotation(SerializedName.class); + if (null != serializedName + && null != field.getAnnotation(Expose.class)) { + serializingFields.add(serializedName.value()); + } } } return serializingFields; diff --git a/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java b/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java index ec9f7999c..54a7d24ed 100644 --- a/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java +++ b/src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java @@ -223,50 +223,13 @@ else if (fieldObject instanceof IJsonBackedObject) { public String serializeObject(@Nonnull final T serializableObject) { Objects.requireNonNull(serializableObject, "parameter serializableObject cannot be null"); logger.logDebug("Serializing type " + serializableObject.getClass().getSimpleName()); - JsonElement outJsonTree = gson.toJsonTree(serializableObject); - - if (serializableObject instanceof IJsonBackedObject) { - outJsonTree = getDataFromAdditionalDataManager(outJsonTree, serializableObject); - } else if (outJsonTree.isJsonObject()) { - final Field[] fields = serializableObject.getClass().getDeclaredFields(); - JsonObject outJson = outJsonTree.getAsJsonObject(); - for(Field field : fields) { - if(outJson.has(field.getName())) { - final Type[] interfaces = field.getType().getGenericInterfaces(); - for(Type interfaceType : interfaces) { - if(interfaceType == IJsonBackedObject.class && outJson.get(field.getName()).isJsonObject()) { - try { - final JsonElement outdatedValue = outJson.remove(field.getName()); - outJson.add(field.getName(), getDataFromAdditionalDataManager(outdatedValue.getAsJsonObject(), field.get(serializableObject))); - } catch (IllegalAccessException ex ) { - logger.logDebug("Couldn't access prop" + field.getName()); - } - break; - } - } - } - } - } - - return outJsonTree.toString(); - } - private JsonElement getDataFromAdditionalDataManager(JsonElement outJsonTree, final T serializableObject) { - final IJsonBackedObject serializableJsonObject = (IJsonBackedObject) serializableObject; - final AdditionalDataManager additionalData = serializableJsonObject.additionalDataManager(); - - // If the item is a valid Graph object, add its additional data - if (outJsonTree.isJsonObject()) { - final JsonObject outJson = outJsonTree.getAsJsonObject(); - - addAdditionalDataFromManagerToJson(additionalData, outJson); - getChildAdditionalData(serializableJsonObject, outJson); - - return outJson; - } else { - return outJsonTree; + final JsonElement outJsonTree = gson.toJsonTree(serializableObject); + if(outJsonTree != null) { + getChildAdditionalData(serializableObject, outJsonTree); + return outJsonTree.toString(); } + return ""; } - /** * Recursively populates additional data for each child object * @@ -274,58 +237,60 @@ private JsonElement getDataFromAdditionalDataManager(JsonElement outJsonTree * @param outJson the serialized output JSON to add to */ @SuppressWarnings("unchecked") - private void getChildAdditionalData(final IJsonBackedObject serializableObject, final JsonObject outJson) { - if(outJson == null) - return; - // Use reflection to iterate through fields for eligible Graph children - for (java.lang.reflect.Field field : serializableObject.getClass().getFields()) { - try { - final Object fieldObject = field.get(serializableObject); - final JsonElement fieldJsonElement = outJson.get(field.getName()); - if(fieldObject == null || fieldJsonElement == null) - continue; - - // If the object is a HashMap, iterate through its children - if (fieldObject instanceof Map && fieldJsonElement.isJsonObject()) { - final Map serializableChildren = (Map) fieldObject; - final Iterator> it = serializableChildren.entrySet().iterator(); - final JsonObject fieldJsonObject = fieldJsonElement.getAsJsonObject(); - - while (it.hasNext()) { - final Map.Entry pair = (Map.Entry)it.next(); - final Object child = pair.getValue(); - final JsonElement childJsonElement = fieldJsonObject.get(pair.getKey().toString()); - // If the item is a valid Graph object, add its additional data - addAdditionalDataFromJsonElementToJson(child, childJsonElement); - } - } - // If the object is a list of Graph objects, iterate through elements - else if (fieldObject instanceof List && fieldJsonElement.isJsonArray()) { - final JsonArray fieldArrayValue = fieldJsonElement.getAsJsonArray(); - final List fieldObjectList = (List) fieldObject; - for (int index = 0; index < fieldObjectList.size(); index++) { - final Object item = fieldObjectList.get(index); - final JsonElement itemJsonElement = fieldArrayValue.get(index); - addAdditionalDataFromJsonElementToJson(item, itemJsonElement); - } - } - // If the object is a valid Graph object, add its additional data - addAdditionalDataFromJsonElementToJson(fieldObject, fieldJsonElement); - } catch (IllegalArgumentException | IllegalAccessException e) { - logger.logError("Unable to access child fields of " + serializableObject.getClass().getSimpleName(), e); - } - } - } + private void getChildAdditionalData(final Object serializableObject, final JsonElement outJson) { + if(outJson == null || serializableObject == null || !outJson.isJsonObject()) + return; + final JsonObject outJsonObject = outJson.getAsJsonObject(); + // Use reflection to iterate through fields for eligible Graph children + for (java.lang.reflect.Field field : serializableObject.getClass().getFields()) { + try { + final Object fieldObject = field.get(serializableObject); + final JsonElement fieldJsonElement = outJsonObject.get(field.getName()); + if(fieldObject == null || fieldJsonElement == null) + continue; + + // If the object is a HashMap, iterate through its children + if (fieldObject instanceof Map && fieldJsonElement.isJsonObject()) { + final Map serializableChildren = (Map) fieldObject; + final Iterator> it = serializableChildren.entrySet().iterator(); + final JsonObject fieldJsonObject = fieldJsonElement.getAsJsonObject(); + + while (it.hasNext()) { + final Map.Entry pair = (Map.Entry)it.next(); + final Object child = pair.getValue(); + final JsonElement childJsonElement = fieldJsonObject.get(pair.getKey().toString()); + // If the item is a valid Graph object, add its additional data + getChildAdditionalData(child, childJsonElement); + } + } + // If the object is a list of Graph objects, iterate through elements + else if (fieldObject instanceof List && fieldJsonElement.isJsonArray()) { + final JsonArray fieldArrayValue = fieldJsonElement.getAsJsonArray(); + final List fieldObjectList = (List) fieldObject; + for (int index = 0; index < fieldObjectList.size(); index++) { + final Object item = fieldObjectList.get(index); + final JsonElement itemJsonElement = fieldArrayValue.get(index); + getChildAdditionalData(item, itemJsonElement); + } + } else if(fieldJsonElement.isJsonObject()) { + // If the object is a valid Graph object, add its additional data + final JsonObject fieldJsonObject = fieldJsonElement.getAsJsonObject(); + addAdditionalDataFromJsonObjectToJson(fieldObject, fieldJsonObject); + } + } catch (IllegalArgumentException | IllegalAccessException e) { + logger.logError("Unable to access child fields of " + serializableObject.getClass().getSimpleName(), e); + } + } + } /** * Add each non-transient additional data property to the given JSON node * * @param item the object containing additional data - * @param itemJsonElement the JSON node to add the additional data properties to + * @param itemJsonObject the JSON node to add the additional data properties to */ - private void addAdditionalDataFromJsonElementToJson (final Object item, final JsonElement itemJsonElement) { - if (item instanceof IJsonBackedObject && itemJsonElement.isJsonObject()) { - final JsonObject itemJsonObject = itemJsonElement.getAsJsonObject(); + private void addAdditionalDataFromJsonObjectToJson (final Object item, final JsonObject itemJsonObject) { + if (item instanceof IJsonBackedObject && itemJsonObject != null) { final IJsonBackedObject serializableItem = (IJsonBackedObject) item; final AdditionalDataManager itemAdditionalData = serializableItem.additionalDataManager(); addAdditionalDataFromManagerToJson(itemAdditionalData, itemJsonObject); diff --git a/src/test/java/com/microsoft/graph/content/BatchRequestContentTest.java b/src/test/java/com/microsoft/graph/content/BatchRequestContentTest.java index e6aa2a98d..176f16f4f 100644 --- a/src/test/java/com/microsoft/graph/content/BatchRequestContentTest.java +++ b/src/test/java/com/microsoft/graph/content/BatchRequestContentTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -14,6 +15,7 @@ import java.util.ArrayList; import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import com.microsoft.graph.authentication.IAuthenticationProvider; import com.microsoft.graph.core.BaseClient; import com.microsoft.graph.core.IBaseClient; @@ -37,12 +39,12 @@ import okhttp3.Response; import okhttp3.ResponseBody; -public class BatchRequestContentTest { +class BatchRequestContentTest { String testurl = "http://graph.microsoft.com/me"; @Test - public void testBatchRequestContentCreation() throws MalformedURLException { + void testBatchRequestContentCreation() throws MalformedURLException { BatchRequestContent requestContent = new BatchRequestContent(); for (int i = 0; i < 5; i++) { IHttpRequest requestStep = mock(IHttpRequest.class); @@ -53,7 +55,7 @@ public void testBatchRequestContentCreation() throws MalformedURLException { } @Test - public void testGetBatchRequestContent() throws MalformedURLException { + void testGetBatchRequestContent() throws MalformedURLException { IHttpRequest requestStep = mock(IHttpRequest.class); when(requestStep.getRequestUrl()).thenReturn(new URL(testurl)); BatchRequestContent requestContent = new BatchRequestContent(); @@ -65,7 +67,7 @@ public void testGetBatchRequestContent() throws MalformedURLException { } @Test - public void testGetBatchRequestContentWithHeader() throws MalformedURLException { + void testGetBatchRequestContentWithHeader() throws MalformedURLException { IHttpRequest requestStep = mock(IHttpRequest.class); when(requestStep.getRequestUrl()).thenReturn(new URL(testurl)); when(requestStep.getHeaders()).thenReturn(Arrays.asList(new HeaderOption("testkey", "testvalue"))); @@ -78,7 +80,7 @@ public void testGetBatchRequestContentWithHeader() throws MalformedURLException } @Test - public void testRemoveBatchRequesStepWithId() throws MalformedURLException { + void testRemoveBatchRequesStepWithId() throws MalformedURLException { IHttpRequest requestStep = mock(IHttpRequest.class); when(requestStep.getRequestUrl()).thenReturn(new URL(testurl)); BatchRequestContent requestContent = new BatchRequestContent(); @@ -90,7 +92,7 @@ public void testRemoveBatchRequesStepWithId() throws MalformedURLException { } @Test - public void testRemoveBatchRequesStepWithIdByAddingMultipleBatchSteps() throws MalformedURLException { + void testRemoveBatchRequesStepWithIdByAddingMultipleBatchSteps() throws MalformedURLException { IHttpRequest requestStep = mock(IHttpRequest.class); when(requestStep.getRequestUrl()).thenReturn(new URL(testurl)); BatchRequestContent requestContent = new BatchRequestContent(); @@ -109,7 +111,7 @@ public void testRemoveBatchRequesStepWithIdByAddingMultipleBatchSteps() throws M } @Test - public void defensiveProgrammingTests() { + void defensiveProgrammingTests() { assertThrows(NullPointerException.class, () -> { new BatchRequestContent().addBatchRequestStep(null); }, "should throw argument exception"); @@ -135,7 +137,7 @@ public void defensiveProgrammingTests() { } @Test - public void executeBatchTest() throws Throwable { + void executeBatchTest() throws Throwable { final BatchRequestContent content = new BatchRequestContent(); IHttpRequest requestStep = mock(IHttpRequest.class); when(requestStep.getRequestUrl()).thenReturn(new URL(testurl)); @@ -164,7 +166,7 @@ public void executeBatchTest() throws Throwable { } @Test - public void usesHttpMethodFromRequestIfAlreadySet() throws MalformedURLException { + void usesHttpMethodFromRequestIfAlreadySet() throws MalformedURLException { IHttpRequest requestStep = mock(IHttpRequest.class); when(requestStep.getRequestUrl()).thenReturn(new URL(testurl)); when(requestStep.getHttpMethod()).thenReturn(HttpMethod.DELETE); @@ -174,13 +176,14 @@ public void usesHttpMethodFromRequestIfAlreadySet() throws MalformedURLException } @Test - public void doesNotThrowWhenTryingToRemoveRequestFromNull() { + void doesNotThrowWhenTryingToRemoveRequestFromNull() { final BatchRequestContent batchRequest = new BatchRequestContent(); batchRequest.removeBatchRequestStepWithId("id"); + assertNull(batchRequest.requests); } @Test - public void doesNotRemoveDependsOnWhenNotEmpty() throws MalformedURLException { + void doesNotRemoveDependsOnWhenNotEmpty() throws MalformedURLException { IHttpRequest requestStep = mock(IHttpRequest.class); when(requestStep.getRequestUrl()).thenReturn(new URL(testurl)); final BatchRequestContent batchRequest = new BatchRequestContent(); @@ -194,7 +197,7 @@ public void doesNotRemoveDependsOnWhenNotEmpty() throws MalformedURLException { } @Test - public void addsContentTypeForBodies() throws MalformedURLException { + void addsContentTypeForBodies() throws MalformedURLException { IHttpRequest requestStep = mock(IHttpRequest.class); when(requestStep.getRequestUrl()).thenReturn(new URL(testurl)); final BatchRequestContent batchRequest = new BatchRequestContent(); @@ -235,4 +238,18 @@ public AdditionalDataManager additionalDataManager() { assertNotNull(step4.headers); assertEquals("application/octet-stream", step4.headers.get("content-type")); } + @Test + void serializesAdditionalData() throws MalformedURLException { + IHttpRequest requestStep = mock(IHttpRequest.class); + when(requestStep.getRequestUrl()).thenReturn(new URL(testurl)); + final BatchRequestContent batchRequest = new BatchRequestContent(); + final String bindValue = "https://somebindvalue"; + final BatchRequestTestBody body = new BatchRequestTestBody(); // using a dynamic implementation doesn't work as "this" maps to the current test class + body.additionalDataManager().put("teamsApp@odata.bind", new JsonPrimitive(bindValue)); + batchRequest.addBatchRequestStep(requestStep, HttpMethod.POST, body); + final ISerializer serializer = new DefaultSerializer(mock(ILogger.class)); + final String result = serializer.serializeObject(batchRequest); + assertNotNull(result); + assertTrue(result.contains(bindValue)); + } } diff --git a/src/test/java/com/microsoft/graph/content/BatchRequestTestBody.java b/src/test/java/com/microsoft/graph/content/BatchRequestTestBody.java new file mode 100644 index 000000000..63f7d8dca --- /dev/null +++ b/src/test/java/com/microsoft/graph/content/BatchRequestTestBody.java @@ -0,0 +1,30 @@ +package com.microsoft.graph.content; + +import javax.annotation.Nullable; + +import com.google.gson.JsonObject; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; +import com.microsoft.graph.serializer.AdditionalDataManager; +import com.microsoft.graph.serializer.IJsonBackedObject; +import com.microsoft.graph.serializer.ISerializer; + +public class BatchRequestTestBody implements IJsonBackedObject { + @Expose + @Nullable + @SerializedName("id") + public String id; + + @Override + public void setRawObject(ISerializer serializer, JsonObject json) { + // TODO Auto-generated method stub + + } + final AdditionalDataManager manager = new AdditionalDataManager(this); + + @Override + public AdditionalDataManager additionalDataManager() { + return manager; + } + +}