java.com.graphql_java_generator.client.GraphQLObjectMapper Maven / Gradle / Ivy
The newest version!
/**
*
*/
package com.graphql_java_generator.client;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.BigIntegerNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.DecimalNode;
import com.fasterxml.jackson.databind.node.DoubleNode;
import com.fasterxml.jackson.databind.node.FloatNode;
import com.fasterxml.jackson.databind.node.IntNode;
import com.fasterxml.jackson.databind.node.LongNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ShortNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.graphql_java_generator.annotation.GraphQLDeprecatedResponseForRequestObject;
import com.graphql_java_generator.exception.GraphQLRequestExecutionException;
/**
* This class is a wrapper around an {@link ObjectMapper}. It allows the GraphQL plugin generated code to use its own
* {@link ObjectMapper}, without interfering with the containing app. This insures that the containing app can configure
* and use "its" {@link ObjectMapper} as it wants, and that the GraphQL plugin can use its own {@link ObjectMapper} with
* its own configuration.
* This class is not Spring bean, as it is configured for each request, with the list of alias for this GraphQL request.
*
* @author etienne-sf
*/
public class GraphQLObjectMapper {
@Autowired
ApplicationContext ctx;
/** The Jackson {@link ObjectMapper} that is specific to the GraphQL response deserialization */
final private ObjectMapper objectMapper;
/**
* This maps contains the {@link Field}, that matches each alias, of each GraphQL type. This allows a proper
* deserialization of each alias value returned in the json response
*/
final Map, Map> aliasFields;
/** The package where the GraphQL objects have been generated */
String graphQLObjectsPackage;
/**
* This class handles various deserialization problems. It's used to manage unknown properties coming in the
* response JSON. These unknown properties are alias defined in the GraphQL query.
*
* @author etienne-sf
*/
public class GraphQLDeserializationProblemHandler extends DeserializationProblemHandler {
private Logger logger = LoggerFactory.getLogger(GraphQLDeserializationProblemHandler.class);
@Override
public boolean handleUnknownProperty(DeserializationContext ctxt, JsonParser p,
JsonDeserializer> deserializer, Object beanOrClass, String propertyName) throws IOException {
Map aliases = null;
Field targetField = null;
JsonDeserialize jsonDeserialize = null;
Object value = null;
if (this.logger.isTraceEnabled()) {
this.logger.trace("Reading alias '" + propertyName + "' for " + beanOrClass.getClass().getName()); //$NON-NLS-1$ //$NON-NLS-2$
}
// Let's check if there is a CustomDeserializer for the field that this alias maps to
if (GraphQLObjectMapper.this.aliasFields != null) {
// If the deprecated response type is generated, then the bean's class is this deprecated response type,
// instead of the query, mutation or subscription type. We have to check that, to retrieve the real
// GraphQL type, and find its custom deserializer, if one was defined.
Class> clazz = beanOrClass.getClass();
GraphQLDeprecatedResponseForRequestObject annotation = clazz
.getAnnotation(GraphQLDeprecatedResponseForRequestObject.class);
if (annotation != null) {
try {
clazz = clazz.getClassLoader().loadClass(annotation.value());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
aliases = GraphQLObjectMapper.this.aliasFields.get(clazz);
}
if (aliases != null) {
targetField = aliases.get(propertyName);
}
if (targetField != null) {
jsonDeserialize = targetField.getAnnotation(JsonDeserialize.class);
}
// If the plugin defined a CustomDeserializer, let's use it
try {
if (jsonDeserialize != null) {
JsonDeserializer> graphQLDeserializer = jsonDeserialize.using().getDeclaredConstructor()
.newInstance();
value = graphQLDeserializer.deserialize(p, ctxt);
} else {
value = getAliasValue(p, targetField, p.readValueAsTree());
}
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException
| GraphQLRequestExecutionException e) {
throw new RuntimeException(e.getMessage(), e);
}
// Let's call the setAliasValue of the target object, to set the alias's value we've just read
String methodName = "setAliasValue"; //$NON-NLS-1$
try {
Method setAliasValue = beanOrClass.getClass().getMethod(methodName, String.class, Object.class);
setAliasValue.invoke(beanOrClass, propertyName, value);
} catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
throw new RuntimeException("Could not find or invoke the method '" + methodName + "' in the " //$NON-NLS-1$ //$NON-NLS-2$
+ beanOrClass.getClass().getName() + " class", e); //$NON-NLS-1$
}
return true;
}
}
/**
* Standard creator for the GraphQL {@link ObjectMapper}
*
* @param graphQLObjectsPackage
* The package where the GraphQL objects have been generated
*/
public GraphQLObjectMapper(String graphQLObjectsPackage, Map, Map> aliasFields) {
this.objectMapper = new ObjectMapper();
this.objectMapper.addHandler(new GraphQLDeserializationProblemHandler());
this.graphQLObjectsPackage = graphQLObjectsPackage;
this.aliasFields = aliasFields;
}
/**
* Parse a TreeNode, and return it as a value, according to the given classes
*
* @param parser
* The current json parser
* @param targetField
* The field on which an alias has been set. This allows to retrieve the annotation on this field, to
* know everything about it's properties, as defined in the GraphQL schema.
* It may be null, in which case enumeration values won't be properly deserialized.
* @param value
* The value to parse
* @return The parsed value. That is, according to the above sample: a String, a List or a
* List>
* @throws IOException
* @throws GraphQLRequestExecutionException
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public Object getAliasValue(JsonParser parser, Field targetField, TreeNode value)
throws IOException, GraphQLRequestExecutionException {
if (value instanceof ArrayNode) {
// value is a list. Let's do a recursive call for each of its item.
List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy