package alien4cloud.json.deserializer;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.Map;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Maps;
/**
* Manages polymorphism deserialization for Jackson through discriminator field (based on field exists).
*/
public class AbstractDiscriminatorPolymorphicDeserializer<T> extends StdDeserializer<T> {
private Map<String, Map<String, Class<? extends T>>> registry = Maps.newHashMap();
private Class<? extends T> valueStringClass = null;
public AbstractDiscriminatorPolymorphicDeserializer(Class<T> clazz) {
super(clazz);
}
protected void addToRegistry(String discriminator, Class<? extends T> clazz) {
addToRegistry(discriminator, "ALL", clazz);
}
protected void addToRegistry(String discriminator, String discriminatorNodeType, Class<? extends T> clazz) {
Map<String, Class<? extends T>> registryForDiscriminator = registry.get(discriminator);
if (registryForDiscriminator == null) {
registryForDiscriminator = Maps.newHashMap();
registry.put(discriminator, registryForDiscriminator);
}
registryForDiscriminator.put(discriminatorNodeType, clazz);
}
/**
* Define the class to use to be used for parsing in case the value is a string and not an object.
*
* @param valueStringClass
*/
protected void setValueStringClass(Class<? extends T> valueStringClass) {
this.valueStringClass = valueStringClass;
}
@Override
public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
if (this.valueStringClass != null && JsonToken.VALUE_STRING.equals(jp.getCurrentToken())) {
String parameter = jp.getValueAsString();
// parse from string value
try {
Constructor constructor = this.valueStringClass.getConstructor(String.class);
return (T) constructor.newInstance(parameter);
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
throw new JsonParseException("Failed to create instance of <" + this.valueStringClass.getName() + "> from constructor using string parameter <"
+ parameter + ">", jp.getCurrentLocation(), e);
}
}
ObjectNode root = mapper.readTree(jp);
Class<? extends T> parameterClass = null;
Iterator<Map.Entry<String, JsonNode>> elementsIterator = root.fields();
while (elementsIterator.hasNext()) {
Map.Entry<String, JsonNode> element = elementsIterator.next();
String name = element.getKey();
String nodeType = element.getValue().getNodeType().toString();
if (registry.containsKey(name)) {
Map<String, Class<? extends T>> registryForDiscriminator = registry.get(name);
if (registryForDiscriminator.containsKey("ALL")) {
parameterClass = registryForDiscriminator.values().iterator().next();
break;
}
if (registryForDiscriminator.containsKey(nodeType)) {
parameterClass = registryForDiscriminator.get(nodeType);
break;
}
}
}
if (parameterClass == null) {
throw new JsonParseException("Failed to find implementation for node " + root + " from registry " + registry, jp.getCurrentLocation());
}
return mapper.treeToValue(root, parameterClass);
}
}