422 lines
13 KiB
Java
422 lines
13 KiB
Java
package org.gcube.informationsystem.serialization;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.io.Reader;
|
|
import java.io.Writer;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import org.gcube.com.fasterxml.jackson.core.JsonGenerationException;
|
|
import org.gcube.com.fasterxml.jackson.core.JsonParseException;
|
|
import org.gcube.com.fasterxml.jackson.core.JsonProcessingException;
|
|
import org.gcube.com.fasterxml.jackson.databind.DeserializationFeature;
|
|
import org.gcube.com.fasterxml.jackson.databind.JavaType;
|
|
import org.gcube.com.fasterxml.jackson.databind.JsonMappingException;
|
|
import org.gcube.com.fasterxml.jackson.databind.JsonNode;
|
|
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
|
|
import org.gcube.com.fasterxml.jackson.databind.SerializationFeature;
|
|
import org.gcube.com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
|
|
import org.gcube.com.fasterxml.jackson.databind.node.ArrayNode;
|
|
import org.gcube.com.fasterxml.jackson.databind.node.ObjectNode;
|
|
import org.gcube.com.fasterxml.jackson.databind.node.TextNode;
|
|
import org.gcube.informationsystem.base.reference.AccessType;
|
|
import org.gcube.informationsystem.base.reference.Element;
|
|
import org.gcube.informationsystem.discovery.Discovery;
|
|
import org.gcube.informationsystem.discovery.knowledge.Knowledge;
|
|
import org.gcube.informationsystem.model.reference.ModelElement;
|
|
import org.gcube.informationsystem.types.TypeMapper;
|
|
import org.gcube.informationsystem.types.reference.Type;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
/**
|
|
* @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;
|
|
|
|
/**
|
|
* @return the ObjectMapper
|
|
*/
|
|
public static ObjectMapper getObjectMapper() {
|
|
return mapper;
|
|
}
|
|
|
|
static {
|
|
mapper = new ObjectMapper();
|
|
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
|
mapper.getSerializationConfig().with(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
|
|
|
|
/*
|
|
* Instructing Jackson to serialize any Date in the defined pattern
|
|
*/
|
|
SimpleDateFormat sdf = new SimpleDateFormat(Element.DATETIME_PATTERN);
|
|
mapper.setDateFormat(sdf);
|
|
|
|
knownTypes = new HashMap<>();
|
|
|
|
List<Package> packages = new ArrayList<Package>();
|
|
|
|
Class<Type> tdClz = Type.class;
|
|
ElementMapper.registerSubtype(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) {
|
|
// ElementMapper.registerSubtypes(clz, dummyClz);
|
|
ElementMapper.registerSubtype(dummyClz);
|
|
}else {
|
|
ElementMapper.registerSubtype(clz);
|
|
}
|
|
packages.add(clz.getPackage());
|
|
}
|
|
|
|
}
|
|
|
|
Set<AccessType> baseTypes = new LinkedHashSet<>();
|
|
baseTypes.add(AccessType.PROPERTY_ELEMENT);
|
|
baseTypes.add(AccessType.ENTITY_ELEMENT);
|
|
baseTypes.add(AccessType.RELATION_ELEMENT);
|
|
|
|
for(AccessType accessType : baseTypes) {
|
|
Class<Element> clz = accessType.getTypeClass();
|
|
try {
|
|
Discovery<? extends Element> discovery = new Discovery<>(clz);
|
|
discovery.addPackages(packages);
|
|
discovery.addDiscoveredElementActions(new ElementMappingAction());
|
|
discovery.discover();
|
|
}catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
try {
|
|
Knowledge.getInstance();
|
|
}catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
|
|
}
|
|
|
|
// This add the ElementDeserializer which has been deprecated thank to the fix in
|
|
// public static <El extends Element> void registerSubtypes(Class<El> clz, Class<El> implementationClass) {
|
|
// String typeName = TypeMapper.getType(clz);
|
|
// SimpleModule isModule = new SimpleModule(typeName);
|
|
// isModule.addDeserializer(clz, new ElementDeserializer<>(clz, mapper));
|
|
// mapper.registerModule(isModule);
|
|
// registerSubtype(implementationClass);
|
|
// }
|
|
|
|
public static <El extends Element> void registerSubtype(Class<El> clz) {
|
|
String typeName = TypeMapper.getType(clz);
|
|
knownTypes.put(typeName, clz);
|
|
mapper.registerSubtypes(clz);
|
|
}
|
|
|
|
/**
|
|
* 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, El extends Element> T marshal(El 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, El extends Element> T marshal(El 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);
|
|
}
|
|
|
|
protected static StringBuffer getError(String unknownType) {
|
|
StringBuffer stringBuffer = new StringBuffer();
|
|
stringBuffer.append(unknownType);
|
|
stringBuffer.append(" is an unknown type. Please provide ");
|
|
stringBuffer.append(ModelElement.SUPERTYPES_PROPERTY);
|
|
stringBuffer.append(" property as string array to allow to instantiate the most appropriated class.");
|
|
return stringBuffer;
|
|
}
|
|
|
|
protected static ObjectNode setTypeToBestAvailable(ObjectNode objectNode) {
|
|
String unknownType = objectNode.get(Element.TYPE_PROPERTY).asText();
|
|
ArrayNode arrayNode = (ArrayNode) objectNode.get(ModelElement.SUPERTYPES_PROPERTY);
|
|
|
|
String candidatedSupertype = null;
|
|
for(int i = 0; i < arrayNode.size(); i++) {
|
|
String superType = arrayNode.get(i).asText();
|
|
if(knownTypes.containsKey(superType)) {
|
|
candidatedSupertype = superType;
|
|
try {
|
|
// Checking if it is one of the base type. In some cases we need to use dummy
|
|
// implementation
|
|
AccessType accessType = AccessType.getAccessType(superType);
|
|
// It is one of the BaseType.
|
|
// Looking if we need to set the dummy implementation class
|
|
if(accessType.getDummyImplementationClass()!=null) {
|
|
// This should not happen because the type has been assigned already to the dummy class.
|
|
candidatedSupertype = accessType.getDummyImplementationClass().getSimpleName();
|
|
}
|
|
} catch(Exception ex) {
|
|
// can continue discovery
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(candidatedSupertype!=null) {
|
|
if(!objectNode.has(ModelElement.EXPECTED_TYPE_PROPERTY)) {
|
|
objectNode.set(ModelElement.EXPECTED_TYPE_PROPERTY, objectNode.get(Element.TYPE_PROPERTY));
|
|
}
|
|
objectNode.set(Element.TYPE_PROPERTY, new TextNode(candidatedSupertype));
|
|
objectNode.remove(ModelElement.SUPERTYPES_PROPERTY);
|
|
return objectNode;
|
|
}
|
|
|
|
StringBuffer stringBuffer = getError(unknownType);
|
|
logger.trace("Unable to unmarshall {}. {}", objectNode.toString(), stringBuffer.toString());
|
|
throw new RuntimeException(stringBuffer.toString());
|
|
}
|
|
|
|
protected static JsonNode analizeTypes(ObjectNode objectNode) {
|
|
String cls = objectNode.get(Element.TYPE_PROPERTY).asText();
|
|
if(!knownTypes.containsKey(cls)) {
|
|
objectNode = setTypeToBestAvailable(objectNode);
|
|
}
|
|
Iterator<String> iterator = objectNode.fieldNames();
|
|
while(iterator.hasNext()) {
|
|
String fieldName = iterator.next();
|
|
JsonNode jn = objectNode.get(fieldName);
|
|
switch (jn.getNodeType()) {
|
|
case OBJECT:
|
|
jn = analizeTypes((ObjectNode)jn);
|
|
break;
|
|
|
|
case ARRAY:
|
|
jn = analizeTypes((ArrayNode) jn);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
objectNode.replace(fieldName, jn);
|
|
}
|
|
return objectNode;
|
|
}
|
|
|
|
protected static ArrayNode analizeTypes(ArrayNode arrayNode) {
|
|
ArrayNode ret = mapper.createArrayNode();
|
|
|
|
for(JsonNode jsonNode : arrayNode) {
|
|
|
|
switch (jsonNode.getNodeType()) {
|
|
case OBJECT:
|
|
jsonNode = analizeTypes((ObjectNode) jsonNode);
|
|
break;
|
|
|
|
case ARRAY:
|
|
jsonNode = analizeTypes((ArrayNode) jsonNode);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret.add(jsonNode);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
try {
|
|
return mapper.readValue(reader, clz);
|
|
} catch (InvalidTypeIdException e) {
|
|
if(!ModelElement.class.isAssignableFrom(clz)) {
|
|
throw e;
|
|
}
|
|
JsonNode jsonNode = mapper.readTree(reader);
|
|
jsonNode = analizeTypes((ObjectNode) jsonNode);
|
|
try {
|
|
return ElementMapper.unmarshal(clz, mapper.writeValueAsString(jsonNode));
|
|
}catch (Throwable t) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
try {
|
|
return mapper.readValue(stream, clz);
|
|
} catch (InvalidTypeIdException e) {
|
|
if(!ModelElement.class.isAssignableFrom(clz)) {
|
|
throw e;
|
|
}
|
|
JsonNode jsonNode = mapper.readTree(stream);
|
|
jsonNode = analizeTypes((ObjectNode) jsonNode);
|
|
try {
|
|
return ElementMapper.unmarshal(clz, mapper.writeValueAsString(jsonNode));
|
|
}catch (Throwable t) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 <El extends Element> El unmarshal(Class<El> clz, String string)
|
|
throws JsonParseException, JsonMappingException, IOException {
|
|
try {
|
|
return mapper.readValue(string, clz);
|
|
} catch (InvalidTypeIdException e) {
|
|
if(!ModelElement.class.isAssignableFrom(clz)) {
|
|
throw e;
|
|
}
|
|
JsonNode jsonNode = mapper.readTree(string);
|
|
jsonNode = analizeTypes((ObjectNode) jsonNode);
|
|
try {
|
|
return ElementMapper.unmarshal(clz, mapper.writeValueAsString(jsonNode));
|
|
}catch (Throwable t) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static <El extends Element> List<El> unmarshalList(Class<El> clz, String string)
|
|
throws JsonParseException, JsonMappingException, IOException {
|
|
JavaType type = mapper.getTypeFactory().constructCollectionType(ArrayList.class, clz);
|
|
try {
|
|
return mapper.readValue(string, type);
|
|
} catch (InvalidTypeIdException e) {
|
|
if(!ModelElement.class.isAssignableFrom(clz)) {
|
|
throw e;
|
|
}
|
|
List<El> ret = new ArrayList<>();
|
|
ArrayNode arrayNode = (ArrayNode) mapper.readTree(string);
|
|
try {
|
|
for(JsonNode jsonNode : arrayNode) {
|
|
jsonNode = analizeTypes((ObjectNode) jsonNode);
|
|
ret.add(ElementMapper.unmarshal(clz, mapper.writeValueAsString(jsonNode)));
|
|
}
|
|
}catch (Throwable t) {
|
|
throw e;
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
public static <El extends Element> List<El> unmarshalList(String string)
|
|
throws JsonParseException, JsonMappingException, IOException {
|
|
JavaType type = mapper.getTypeFactory().constructCollectionType(ArrayList.class, Element.class);
|
|
try {
|
|
return mapper.readValue(string, type);
|
|
} catch (InvalidTypeIdException e) {
|
|
List<El> ret = new ArrayList<>();
|
|
ArrayNode arrayNode = (ArrayNode) mapper.readTree(string);
|
|
try {
|
|
for(JsonNode jsonNode : arrayNode) {
|
|
jsonNode = analizeTypes((ObjectNode) jsonNode);
|
|
ret.add((El) ElementMapper.unmarshal(Element.class, mapper.writeValueAsString(jsonNode)));
|
|
}
|
|
}catch (Throwable t) {
|
|
throw e;
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
}
|