Skip to content

Commit 29d9b8f

Browse files
Merge pull request #249 from PrimosK/dev
Sprout for fix #248
2 parents 5a4a79f + 69df140 commit 29d9b8f

16 files changed

+422
-86
lines changed

src/main/java/com/microsoft/graph/serializer/CollectionPageSerializer.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
import java.lang.reflect.InvocationTargetException;
2626
import java.lang.reflect.Type;
27-
import java.util.ArrayList;
2827
import java.util.List;
2928
import java.util.Objects;
3029

@@ -112,7 +111,7 @@ public static <T1, T2 extends BaseRequestBuilder<T1>> BaseCollectionPage<T1, T2>
112111
final Class<?> responseClass = Class.forName(responseClassCanonicalName);
113112
final JsonObject responseJson = new JsonObject();
114113
responseJson.add("value", json);
115-
final BaseCollectionResponse<T1> response = CollectionResponseSerializer.deserialize(responseJson, responseClass, logger);
114+
final BaseCollectionResponse<T1> response = CollectionResponseDeserializer.deserialize(responseJson, responseClass, logger);
116115
/** eg: com.microsoft.graph.requests.AttachmentCollectionRequestBuilder */
117116
final String responseBuilderCanonicalName = responseClassCanonicalName
118117
.substring(0, responseClassCanonicalName.length() - responseLength) + "RequestBuilder";

src/main/java/com/microsoft/graph/serializer/CollectionResponseSerializer.java renamed to src/main/java/com/microsoft/graph/serializer/CollectionResponseDeserializer.java

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@
3737
import com.microsoft.graph.http.BaseCollectionResponse;
3838
import com.microsoft.graph.logger.ILogger;
3939

40-
/** Specialized serializer to handle collection responses */
41-
public class CollectionResponseSerializer {
40+
/** Specialized de-serializer to handle collection responses */
41+
public class CollectionResponseDeserializer {
4242
private static DefaultSerializer serializer;
4343
/**
4444
* Not available for instantiation
4545
*/
46-
private CollectionResponseSerializer() {}
46+
private CollectionResponseDeserializer() {}
4747
/**
4848
* Deserializes the JsonElement
4949
*
@@ -86,15 +86,7 @@ public static <T1> BaseCollectionResponse<T1> deserialize(@Nonnull final JsonEle
8686
for(JsonElement sourceElement : sourceArray) {
8787
if(sourceElement.isJsonObject()) {
8888
final JsonObject sourceObject = sourceElement.getAsJsonObject();
89-
Class<?> entityClass = serializer.getDerivedClass(sourceObject, baseEntityClass);
90-
if(entityClass == null) {
91-
if(baseEntityClass == null) {
92-
logger.logError("Could not find target class for object " + sourceObject.toString(), null);
93-
continue;
94-
} else
95-
entityClass = baseEntityClass; // it is possible the odata type is absent or we can't find the derived type (not in SDK yet)
96-
}
97-
final T1 targetObject = (T1)serializer.deserializeObject(sourceObject, entityClass);
89+
final T1 targetObject = (T1)serializer.deserializeObject(sourceObject, baseEntityClass);
9890
((IJsonBackedObject)targetObject).setRawObject(serializer, sourceObject);
9991
list.add(targetObject);
10092
} else if (sourceElement.isJsonPrimitive()) {

src/main/java/com/microsoft/graph/serializer/DefaultSerializer.java

Lines changed: 12 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,10 @@
2222

2323
package com.microsoft.graph.serializer;
2424

25-
import com.google.common.base.CaseFormat;
2625
import com.google.gson.Gson;
2726
import com.google.gson.JsonArray;
2827
import com.google.gson.JsonElement;
2928
import com.google.gson.JsonObject;
30-
3129
import com.microsoft.graph.logger.ILogger;
3230

3331
import java.io.IOException;
@@ -38,27 +36,28 @@
3836
import java.util.Iterator;
3937
import java.util.List;
4038
import java.util.Map;
41-
import java.util.Objects;
4239
import java.util.Map.Entry;
43-
40+
import java.util.Objects;
4441
import javax.annotation.Nonnull;
4542
import javax.annotation.Nullable;
4643

4744
/**
4845
* The default serializer implementation for the SDK
4946
*/
5047
public class DefaultSerializer implements ISerializer {
51-
private static final String graphResponseHeadersKey = "graphResponseHeaders";
48+
49+
private static final String GRAPH_RESPONSE_HEADERS_KEY = "graphResponseHeaders";
50+
51+
/**
52+
* The logger
53+
*/
54+
private final ILogger logger;
5255

5356
/**
5457
* The instance of the internal serializer
5558
*/
5659
private final Gson gson;
5760

58-
/**
59-
* The logger
60-
*/
61-
private final ILogger logger;
6261

6362
/**
6463
* Creates a DefaultSerializer
@@ -104,16 +103,7 @@ public <T> T deserializeObject(@Nonnull final JsonElement rawElement, @Nonnull f
104103
if (jsonObject instanceof IJsonBackedObject) {
105104
logger.logDebug("Deserializing type " + clazz.getSimpleName());
106105
final JsonObject rawObject = rawElement.isJsonObject() ? rawElement.getAsJsonObject() : null;
107-
108-
// If there is a derived class, try to get it and deserialize to it
109-
T jo = jsonObject;
110-
if (rawElement.isJsonObject()) {
111-
final Class<?> derivedClass = this.getDerivedClass(rawObject, clazz);
112-
if (derivedClass != null)
113-
jo = (T) gson.fromJson(rawElement, derivedClass);
114-
}
115-
116-
final IJsonBackedObject jsonBackedObject = (IJsonBackedObject) jo;
106+
final IJsonBackedObject jsonBackedObject = (IJsonBackedObject) jsonObject;
117107

118108
if(rawElement.isJsonObject()) {
119109
jsonBackedObject.setRawObject(this, rawObject);
@@ -123,9 +113,9 @@ public <T> T deserializeObject(@Nonnull final JsonElement rawElement, @Nonnull f
123113

124114
if (responseHeaders != null) {
125115
JsonElement convertedHeaders = gson.toJsonTree(responseHeaders);
126-
jsonBackedObject.additionalDataManager().put(graphResponseHeadersKey, convertedHeaders);
116+
jsonBackedObject.additionalDataManager().put(GRAPH_RESPONSE_HEADERS_KEY, convertedHeaders);
127117
}
128-
return jo;
118+
return jsonObject;
129119
} else {
130120
logger.logDebug("Deserializing a non-IJsonBackedObject type " + clazz.getSimpleName());
131121
return jsonObject;
@@ -304,52 +294,12 @@ private void addAdditionalDataFromJsonObjectToJson (final Object item, final Jso
304294
*/
305295
private void addAdditionalDataFromManagerToJson(AdditionalDataManager additionalDataManager, JsonObject jsonNode) {
306296
for (Map.Entry<String, JsonElement> entry : additionalDataManager.entrySet()) {
307-
if(!entry.getKey().equals(graphResponseHeadersKey)) {
297+
if(!entry.getKey().equals(GRAPH_RESPONSE_HEADERS_KEY)) {
308298
jsonNode.add(entry.getKey(), entry.getValue());
309299
}
310300
}
311301
}
312302

313-
private final static String ODATA_TYPE_KEY = "@odata.type";
314-
/**
315-
* Get the derived class for the given JSON object
316-
* This covers scenarios in which the service may return one of several derived types
317-
* of a base object, which it defines using the odata.type parameter
318-
*
319-
* @param jsonObject the raw JSON object of the response
320-
* @param parentClass the parent class the derived class should inherit from
321-
* @return the derived class if found, or null if not applicable
322-
*/
323-
@Nullable
324-
public Class<?> getDerivedClass(@Nonnull final JsonObject jsonObject, @Nullable final Class<?> parentClass) {
325-
Objects.requireNonNull(jsonObject, "parameter jsonObject cannot be null");
326-
//Identify the odata.type information if provided
327-
if (jsonObject.get(ODATA_TYPE_KEY) != null) {
328-
/** #microsoft.graph.user or #microsoft.graph.callrecords.callrecord */
329-
final String odataType = jsonObject.get(ODATA_TYPE_KEY).getAsString();
330-
final int lastDotIndex = odataType.lastIndexOf(".");
331-
final String derivedType = (odataType.substring(0, lastDotIndex) +
332-
".models." +
333-
CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL,
334-
odataType.substring(lastDotIndex + 1)))
335-
.replace("#", "com.");
336-
try {
337-
Class<?> derivedClass = Class.forName(derivedType);
338-
//Check that the derived class inherits from the given parent class
339-
if (parentClass == null || parentClass.isAssignableFrom(derivedClass)) {
340-
return derivedClass;
341-
}
342-
return null;
343-
} catch (ClassNotFoundException e) {
344-
logger.logDebug("Unable to find a corresponding class for derived type " + derivedType + ". Falling back to parent class.");
345-
//If we cannot determine the derived type to cast to, return null
346-
//This may happen if the API and the SDK are out of sync
347-
return null;
348-
}
349-
}
350-
//If there is no defined OData type, return null
351-
return null;
352-
}
353303

354304
/**
355305
* Gets the logger in use
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.microsoft.graph.serializer;
2+
3+
import com.google.common.base.CaseFormat;
4+
import com.google.gson.JsonObject;
5+
import com.microsoft.graph.logger.ILogger;
6+
7+
import java.util.Objects;
8+
import javax.annotation.Nonnull;
9+
import javax.annotation.Nullable;
10+
11+
public class DerivedClassIdentifier {
12+
13+
private final static String ODATA_TYPE_KEY = "@odata.type";
14+
15+
private final ILogger logger;
16+
17+
public DerivedClassIdentifier(@Nonnull ILogger logger) {
18+
this.logger = Objects.requireNonNull(logger, "logger parameter cannot be null");;
19+
}
20+
21+
/**
22+
* Get the derived class for the given JSON object
23+
* This covers scenarios in which the service may return one of several derived types
24+
* of a base object, which it defines using the odata.type parameter
25+
*
26+
* @param jsonObject the raw JSON object of the response
27+
* @param parentClass the parent class the derived class should inherit from
28+
* @return the derived class if found, or null if not applicable
29+
*/
30+
@Nullable
31+
public Class<?> identify(@Nonnull final JsonObject jsonObject, @Nullable final Class<?> parentClass) {
32+
Objects.requireNonNull(jsonObject, "parameter jsonObject cannot be null");
33+
//Identify the odata.type information if provided
34+
if (jsonObject.get(ODATA_TYPE_KEY) != null) {
35+
/** #microsoft.graph.user or #microsoft.graph.callrecords.callrecord */
36+
final String odataType = jsonObject.get(ODATA_TYPE_KEY).getAsString();
37+
final int lastDotIndex = odataType.lastIndexOf(".");
38+
final String derivedType = (odataType.substring(0, lastDotIndex) +
39+
".models." +
40+
CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL,
41+
odataType.substring(lastDotIndex + 1)))
42+
.replace("#", "com.");
43+
try {
44+
Class<?> derivedClass = Class.forName(derivedType);
45+
//Check that the derived class inherits from the given parent class
46+
if (parentClass == null || parentClass.isAssignableFrom(derivedClass)) {
47+
return derivedClass;
48+
}
49+
return null;
50+
} catch (ClassNotFoundException e) {
51+
logger.logDebug("Unable to find a corresponding class for derived type " + derivedType + ". Falling back to parent class.");
52+
//If we cannot determine the derived type to cast to, return null
53+
//This may happen if the API and the SDK are out of sync
54+
return null;
55+
}
56+
}
57+
//If there is no defined OData type, return null
58+
return null;
59+
}
60+
}

src/main/java/com/microsoft/graph/serializer/FallbackTypeAdapterFactory.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,23 @@
2222

2323
package com.microsoft.graph.serializer;
2424

25-
import java.io.IOException;
26-
import java.util.HashMap;
27-
import java.util.Map;
28-
import java.util.Objects;
29-
3025
import com.google.common.base.CaseFormat;
3126
import com.google.gson.Gson;
3227
import com.google.gson.TypeAdapter;
3328
import com.google.gson.TypeAdapterFactory;
29+
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
3430
import com.google.gson.reflect.TypeToken;
3531
import com.google.gson.stream.JsonReader;
3632
import com.google.gson.stream.JsonToken;
3733
import com.google.gson.stream.JsonWriter;
3834
import com.microsoft.graph.logger.ILogger;
3935

40-
import javax.annotation.Nullable;
36+
import java.io.IOException;
37+
import java.util.HashMap;
38+
import java.util.Map;
39+
import java.util.Objects;
4140
import javax.annotation.Nonnull;
41+
import javax.annotation.Nullable;
4242

4343
/**
4444
* Handles serialization/deserialization for special types (especially of
@@ -67,14 +67,14 @@ public void write(JsonWriter out, Void value) throws IOException {
6767
}
6868

6969
@Override
70-
public Void read(JsonReader in) throws IOException {
70+
public Void read(JsonReader in) {
7171
return null;
7272
}
7373

7474
};
7575

7676
/**
77-
* Instanciates a new type adapter factory
77+
* Instantiates a new type adapter factory
7878
*
7979
* @param logger logger to use for the factory
8080
*/
@@ -89,10 +89,21 @@ public FallbackTypeAdapterFactory(@Nonnull final ILogger logger) {
8989
public <T> TypeAdapter<T> create(@Nonnull final Gson gson, @Nonnull final TypeToken<T> type) {
9090
Objects.requireNonNull(type, "parameter type cannot be null");
9191
final Class<T> rawType = (Class<T>) type.getRawType();
92+
9293
if (rawType.isEnum()) {
93-
return new EnumTypeAdapter<T>(rawType, logger);
94+
return new EnumTypeAdapter<>(rawType, logger);
9495
} else if (rawType == Void.class) {
9596
return (TypeAdapter<T>) voidAdapter;
97+
} else if (IJsonBackedObject.class.isAssignableFrom(type.getRawType())) {
98+
99+
final TypeAdapter<IJsonBackedObject> delegatedAdapter = (TypeAdapter<IJsonBackedObject>) gson.getDelegateAdapter(this, type);
100+
101+
// Avoid overriding custom IJsonBackedObject type adapters defined in GsonFactory
102+
if (!(delegatedAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
103+
return null;
104+
}
105+
106+
return (TypeAdapter<T>) new ODataTypeParametrizedIJsonBackedTypedAdapter(this, gson, delegatedAdapter, (TypeToken<IJsonBackedObject>) type, logger);
96107
}
97108
else {
98109
return null;

src/main/java/com/microsoft/graph/serializer/GsonFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ public JsonElement serialize(final BaseCollectionPage<?, ?> src,
239239
public BaseCollectionResponse<?> deserialize(final JsonElement json,
240240
final Type typeOfT,
241241
final JsonDeserializationContext context) throws JsonParseException {
242-
return CollectionResponseSerializer.deserialize(json, typeOfT, logger);
242+
return CollectionResponseDeserializer.deserialize(json, typeOfT, logger);
243243
}
244244
};
245245

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.microsoft.graph.serializer;
2+
3+
import com.google.gson.Gson;
4+
import com.google.gson.JsonElement;
5+
import com.google.gson.TypeAdapter;
6+
import com.google.gson.internal.Streams;
7+
import com.google.gson.reflect.TypeToken;
8+
import com.google.gson.stream.JsonReader;
9+
import com.google.gson.stream.JsonWriter;
10+
import com.microsoft.graph.logger.ILogger;
11+
12+
import java.io.IOException;
13+
import java.util.Objects;
14+
import javax.annotation.Nonnull;
15+
16+
/**
17+
* This adapter is responsible for deserialization of IJsonBackedObjects where service
18+
* returns one of several derived types of a base object, which is defined using the
19+
* odata.type parameter. If odata.type parameter is not found, the Gson default
20+
* (delegated) type adapter is used.
21+
*/
22+
class ODataTypeParametrizedIJsonBackedTypedAdapter extends TypeAdapter<IJsonBackedObject> {
23+
24+
private final FallbackTypeAdapterFactory fallbackTypeAdapterFactory;
25+
private final Gson gson;
26+
private final TypeAdapter<IJsonBackedObject> delegatedAdapter;
27+
private final TypeToken<IJsonBackedObject> type;
28+
private final DerivedClassIdentifier derivedClassIdentifier;
29+
30+
public ODataTypeParametrizedIJsonBackedTypedAdapter(FallbackTypeAdapterFactory fallbackTypeAdapterFactory, @Nonnull Gson gson,
31+
@Nonnull TypeAdapter<IJsonBackedObject> delegatedAdapter, @Nonnull final TypeToken<IJsonBackedObject> type, @Nonnull final ILogger logger)
32+
{
33+
super();
34+
this.fallbackTypeAdapterFactory = fallbackTypeAdapterFactory;
35+
this.gson = Objects.requireNonNull(gson, "parameter gson cannot be null");
36+
this.delegatedAdapter = Objects.requireNonNull(delegatedAdapter, "object delegated adapted cannot be null");
37+
this.type = Objects.requireNonNull(type, "object type cannot be null");
38+
this.derivedClassIdentifier = new DerivedClassIdentifier(logger);
39+
}
40+
41+
@Override
42+
public void write(JsonWriter out, IJsonBackedObject value)
43+
throws IOException
44+
{
45+
this.delegatedAdapter.write(out, value);
46+
}
47+
48+
@Override
49+
public IJsonBackedObject read(JsonReader in) {
50+
JsonElement jsonElement = Streams.parse(in);
51+
52+
if (jsonElement.isJsonObject()) {
53+
final Class<?> derivedClass = derivedClassIdentifier.identify(jsonElement.getAsJsonObject(), type.getRawType());
54+
55+
if (derivedClass != null) {
56+
final TypeAdapter<?> subTypeAdapter = gson.getDelegateAdapter(fallbackTypeAdapterFactory, TypeToken.get(derivedClass));
57+
return (IJsonBackedObject) subTypeAdapter.fromJsonTree(jsonElement);
58+
}
59+
}
60+
61+
return delegatedAdapter.fromJsonTree(jsonElement);
62+
}
63+
}

0 commit comments

Comments
 (0)