information-system-model/src/main/java/org/gcube/informationsystem/utils/ElementMapper.java

366 lines
12 KiB
Java

package org.gcube.informationsystem.utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import org.gcube.informationsystem.base.reference.AccessType;
import org.gcube.informationsystem.base.reference.Element;
import org.gcube.informationsystem.types.TypeMapper;
import org.gcube.informationsystem.types.reference.Type;
import org.gcube.informationsystem.utils.discovery.ElementSpecilizationDiscovery;
import org.gcube.informationsystem.utils.discovery.RegistrationProvider;
import org.gcube.informationsystem.utils.discovery.SchemaAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
/**
* @author Luca Frosini (ISTI - CNR)
*/
@SuppressWarnings("unchecked")
public abstract class ElementMapper {
private static Logger logger = LoggerFactory.getLogger(ElementMapper.class);
protected static final ObjectMapper mapper;
protected static final Map<String, Class<? extends Element>> knownTypes;
// // protected static final Map<Class<? extends Element>, Class<? extends ElementImpl>> interfaceToImplementation;
// protected static final Map<String, Class<? extends ElementImpl>> nameToImplementation;
/**
* @return the ObjectMapper
*/
public static ObjectMapper getObjectMapper() {
return mapper;
}
static {
mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
knownTypes = new HashMap<>();
//interfaceToImplementation = new HashMap<>();
// nameToImplementation = new HashMap<>();
List<Package> packages = new ArrayList<Package>();
/*
Class<Type> tdClz = Type.class;
ElementMapper.registerSubtypes(tdClz);
packages.add(tdClz.getPackage());
*/
AccessType[] accessTypes = AccessType.values();
for(AccessType accessType : accessTypes) {
@SuppressWarnings("rawtypes")
Class clz = accessType.getTypeClass();
if(!Type.class.isAssignableFrom(clz)) {
Class<Element> dummyClz = accessType.getDummyImplementationClass();
if(dummyClz != null) {
SimpleModule isModule = new SimpleModule(accessType.getName());
isModule.addDeserializer(clz, new ElementDeserializer<>(clz, mapper));
mapper.registerModule(isModule);
ElementMapper.registerSubtypes(dummyClz);
}
/* else {
ElementMapper.registerSubtypes(clz);
}*/
packages.add(clz.getPackage());
}
}
registerPackages(packages);
ServiceLoader<? extends RegistrationProvider> regsitrationProviders = ServiceLoader
.load(RegistrationProvider.class);
for(RegistrationProvider registrationProvider : regsitrationProviders) {
registerPackages(registrationProvider.getPackagesToRegister());
}
}
public static void registerPackages(List<Package> packages) {
SchemaAction schemaAction = new ElementMappingAction();
try {
ElementSpecilizationDiscovery.manageISM(schemaAction, packages);
} catch(Exception e) {
logger.error("Error registering types", e);
}
}
public static void registerPackages(Package... packages) {
SchemaAction schemaAction = new ElementMappingAction();
try {
ElementSpecilizationDiscovery.manageISM(schemaAction, packages);
} catch(Exception e) {
logger.error("Error registering types", e);
}
}
public static <El extends Element> void registerSubtypes(Class<El>... classes) {
for(Class<El> clz : classes) {
String typeName = TypeMapper.getType(clz);
knownTypes.put(typeName, clz);
// if(clz.isAnnotationPresent(JsonDeserialize.class)) {
// JsonDeserialize jsonDeserialize = clz.getAnnotation(JsonDeserialize.class);
// Class<? extends ElementImpl> impl = (Class<? extends ElementImpl>) jsonDeserialize.as();
// nameToImplementation.put(typeName, impl);
// }
}
mapper.registerSubtypes(classes);
}
/**
* Write the serialization of a given resource to a given
* {@link OutputStream} .
*
* @param object the resource
* @param stream the stream in input
* @throws IOException
* @throws JsonMappingException
* @throws JsonGenerationException
*/
public static <T extends OutputStream, ISM extends Element> T marshal(ISM object, T stream)
throws JsonGenerationException, JsonMappingException, IOException {
mapper.writeValue(stream, object);
return stream;
}
/**
* Write the serialization of a given resource to a given {@link Writer} .
* @param object the resource
* @param writer the writer in input
* @throws IOException
* @throws JsonMappingException
* @throws JsonGenerationException
*/
public static <T extends Writer, ISM extends Element> T marshal(ISM object, T writer)
throws JsonGenerationException, JsonMappingException, IOException {
mapper.writeValue(writer, object);
return writer;
}
/**
* Return the String serialization of a given object
* @param object the object to marshal
* @return the String serialization of a given resource
* @throws JsonProcessingException
*/
public static <El extends Element> String marshal(El object) throws JsonProcessingException {
return mapper.writeValueAsString(object);
}
/**
* Return the String serialization of a given list
* @param list the list to marshal
* @return the String serialization of a given list
* @throws JsonProcessingException
*/
public static <El extends Element> String marshal(List<El> list) throws JsonProcessingException {
JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, Element.class);
return mapper.writerFor(type).writeValueAsString(list);
}
/**
* Return the String serialization of a given array
* @param array the array to marshal
* @return the String serialization of a given array
* @throws JsonProcessingException
*/
public static <El extends Element> String marshal(El[] array) throws JsonProcessingException {
return mapper.writeValueAsString(array);
}
/**
* Creates a resource of given class from its serialization in a given
* {@link Reader}.
* @param clz the class of the resource
* @param reader the reader
* @return the resource
* @throws JsonParseException
* @throws JsonMappingException
* @throws IOException
*/
public static <El extends Element> El unmarshal(Class<El> clz, Reader reader)
throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(reader, clz);
}
/**
* Creates a resource of given class from its serialization in a given
* {@link InputStream}.
* @param clz the class of the resource
* @param stream the stream
* @return the resource
* @throws IOException
* @throws JsonMappingException
* @throws JsonParseException
*/
public static <El extends Element> El unmarshal(Class<El> clz, InputStream stream)
throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(stream, clz);
}
public static JsonNode analizeJsonToReplaceType(JsonNode jsonNodeToAnalize, String typeIdToReplace) throws Exception {
boolean replaced = false;
String candidatedSuperClass = null;
int index = 0;
String typeId = null;
if(jsonNodeToAnalize.has(Element.CLASS_PROPERTY)) {
typeId = jsonNodeToAnalize.get(Element.CLASS_PROPERTY).asText();
}
if(typeId != null && typeIdToReplace.compareTo(typeId) == 0) {
JsonNode superClassesTreeNode = jsonNodeToAnalize.get(Element.SUPERCLASSES_PROPERTY);
if(superClassesTreeNode != null && superClassesTreeNode.isArray()) {
ArrayNode arrayNode = (ArrayNode) superClassesTreeNode;
for(int i = 0; i < arrayNode.size(); i++) {
try {
JsonNode jsonNode = arrayNode.get(i);
JsonNodeType jsonNodeType = jsonNode.getNodeType();
switch(jsonNodeType) {
case STRING:
String superClass = jsonNode.asText();
try {
Enum.valueOf(AccessType.class, superClass.toUpperCase());
// It is one of the BaseType. Looking for
// another type because the base one
continue;
} catch(Exception ex) {
// can continue discovery
}
if(knownTypes.containsKey(superClass)) {
candidatedSuperClass = superClass;
index = i;
}
break;
default:
break;
}
if(candidatedSuperClass!=null) {
break;
}
} catch(Exception ex) {
// Trying the next one
}
}
arrayNode.remove(index);
((ObjectNode) jsonNodeToAnalize).set(Element.CLASS_PROPERTY, new TextNode(candidatedSuperClass));
replaced = true;
}
}
if(!replaced) {
// continue to search inside the object
Iterator<String> iterator = jsonNodeToAnalize.fieldNames();
while(iterator.hasNext()) {
String fieldName = iterator.next();
JsonNode jn = jsonNodeToAnalize.get(fieldName);
if(jn.getNodeType() == JsonNodeType.OBJECT) {
try {
JsonNode newValue = analizeJsonToReplaceType(jn,typeIdToReplace);
replaced = true;
((ObjectNode) jsonNodeToAnalize).set(fieldName, newValue);
break;
}catch (Exception e) {
continue;
}
}
}
}
if(!replaced) {
throw new Exception();
}
return jsonNodeToAnalize;
}
/**
* Creates a resource of given class from its serialization in a given String
* @param clz the class of the resource
* @param string
* @return the resource
* @throws JsonParseException
* @throws JsonMappingException
* @throws IOException
*/
public static <ISM extends Element> ISM unmarshal(Class<ISM> clz, String string)
throws JsonParseException, JsonMappingException, IOException {
try {
return mapper.readValue(string, clz);
} catch(InvalidTypeIdException e) {
String typeId = e.getTypeId();
JsonNode jsonNode = mapper.readTree(string);
try {
jsonNode = analizeJsonToReplaceType(jsonNode, typeId);
}catch (Exception ex) {
throw e;
}
return ElementMapper.unmarshal(clz, mapper.writeValueAsString(jsonNode));
}
}
/**
* Creates a resource of given class from its serialization in a given String
* @param clz the class of the resource
* @param string
* @return the resource
* @throws JsonParseException
* @throws JsonMappingException
* @throws IOException
*/
public static <ISM extends Element> ISM unmarshalWithReader(Class<ISM> clz, String string)
throws JsonParseException, JsonMappingException, IOException {
return mapper.readerFor(clz).readValue(string);
}
public static <ISM extends Element> List<ISM> unmarshalList(Class<ISM> clz, String string)
throws JsonParseException, JsonMappingException, IOException {
JavaType type = mapper.getTypeFactory().constructCollectionType(ArrayList.class, clz);
return mapper.readValue(string, type);
}
public static <ISM extends Element> List<ISM> unmarshalList(String string)
throws JsonParseException, JsonMappingException, IOException {
JavaType type = mapper.getTypeFactory().constructCollectionType(ArrayList.class, Element.class);
return mapper.readValue(string, type);
}
}