Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -112,7 +111,7 @@ public static <T1, T2 extends BaseRequestBuilder<T1>> BaseCollectionPage<T1, T2>
final Class<?> responseClass = Class.forName(responseClassCanonicalName);
final JsonObject responseJson = new JsonObject();
responseJson.add("value", json);
final BaseCollectionResponse<T1> response = CollectionResponseSerializer.deserialize(responseJson, responseClass, logger);
final BaseCollectionResponse<T1> response = CollectionResponseDeserializer.deserialize(responseJson, responseClass, logger);
/** eg: com.microsoft.graph.requests.AttachmentCollectionRequestBuilder */
final String responseBuilderCanonicalName = responseClassCanonicalName
.substring(0, responseClassCanonicalName.length() - responseLength) + "RequestBuilder";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -86,15 +86,7 @@ public static <T1> BaseCollectionResponse<T1> 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()) {
Expand Down
74 changes: 12 additions & 62 deletions src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -38,27 +36,28 @@
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;

/**
* 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
Expand Down Expand Up @@ -104,16 +103,7 @@ public <T> 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);
Expand All @@ -123,9 +113,9 @@ public <T> 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;
Expand Down Expand Up @@ -304,52 +294,12 @@ private void addAdditionalDataFromJsonObjectToJson (final Object item, final Jso
*/
private void addAdditionalDataFromManagerToJson(AdditionalDataManager additionalDataManager, JsonObject jsonNode) {
for (Map.Entry<String, JsonElement> 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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +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(@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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
*/
Expand All @@ -89,10 +89,21 @@ public FallbackTypeAdapterFactory(@Nonnull final ILogger logger) {
public <T> TypeAdapter<T> create(@Nonnull final Gson gson, @Nonnull final TypeToken<T> type) {
Objects.requireNonNull(type, "parameter type cannot be null");
final Class<T> rawType = (Class<T>) type.getRawType();

if (rawType.isEnum()) {
return new EnumTypeAdapter<T>(rawType, logger);
return new EnumTypeAdapter<>(rawType, logger);
} else if (rawType == Void.class) {
return (TypeAdapter<T>) voidAdapter;
} else if (IJsonBackedObject.class.isAssignableFrom(type.getRawType())) {

final TypeAdapter<IJsonBackedObject> delegatedAdapter = (TypeAdapter<IJsonBackedObject>) gson.getDelegateAdapter(this, type);

// Avoid overriding custom IJsonBackedObject type adapters defined in GsonFactory
if (!(delegatedAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
return null;
}

return (TypeAdapter<T>) new ODataTypeParametrizedIJsonBackedTypedAdapter(this, gson, delegatedAdapter, (TypeToken<IJsonBackedObject>) type, logger);
}
else {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -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<IJsonBackedObject> {

private final FallbackTypeAdapterFactory fallbackTypeAdapterFactory;
private final Gson gson;
private final TypeAdapter<IJsonBackedObject> delegatedAdapter;
private final TypeToken<IJsonBackedObject> type;
private final DerivedClassIdentifier derivedClassIdentifier;

public ODataTypeParametrizedIJsonBackedTypedAdapter(FallbackTypeAdapterFactory fallbackTypeAdapterFactory, @Nonnull Gson gson,
@Nonnull TypeAdapter<IJsonBackedObject> delegatedAdapter, @Nonnull final TypeToken<IJsonBackedObject> 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);
}
}
Loading