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

355 lines
12 KiB
Java
Raw Normal View History

2023-02-03 14:39:25 +01:00
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;
2022-06-13 10:21:21 +02:00
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
2023-02-06 16:35:30 +01:00
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
2023-02-06 16:35:30 +01:00
import java.util.Set;
2020-07-07 17:04:25 +02:00
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;
2023-04-27 14:46:08 +02:00
import org.gcube.com.fasterxml.jackson.databind.SerializationFeature;
2020-07-07 17:04:25 +02:00
import org.gcube.com.fasterxml.jackson.databind.module.SimpleModule;
import org.gcube.com.fasterxml.jackson.databind.node.ArrayNode;
import org.gcube.com.fasterxml.jackson.databind.node.JsonNodeType;
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;
2023-02-06 16:35:30 +01:00
import org.gcube.informationsystem.discovery.Discovery;
2023-02-08 17:56:13 +01:00
import org.gcube.informationsystem.discovery.knowledge.Knowledge;
import org.gcube.informationsystem.types.TypeMapper;
2020-02-03 10:51:29 +01:00
import org.gcube.informationsystem.types.reference.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Luca Frosini (ISTI - CNR)
*/
@SuppressWarnings("unchecked")
2020-02-03 10:51:29 +01:00
public abstract class ElementMapper {
2020-02-03 10:51:29 +01:00
private static Logger logger = LoggerFactory.getLogger(ElementMapper.class);
protected static final ObjectMapper mapper;
protected static final Map<String, Class<? extends Element>> knownTypes;
2023-02-07 15:45:02 +01:00
/**
* @return the ObjectMapper
*/
public static ObjectMapper getObjectMapper() {
return mapper;
}
static {
mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
2023-04-27 14:46:08 +02:00
mapper.getSerializationConfig().with(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
2022-06-13 10:21:21 +02:00
/*
* Instructing Jackson to serialize any Date in the defined pattern
*/
SimpleDateFormat sdf = new SimpleDateFormat(Element.DATETIME_PATTERN);
mapper.setDateFormat(sdf);
knownTypes = new HashMap<>();
2019-10-30 09:57:14 +01:00
List<Package> packages = new ArrayList<Package>();
2020-02-03 10:51:29 +01:00
Class<Type> tdClz = Type.class;
2020-07-02 19:17:57 +02:00
ElementMapper.registerSubtype(tdClz);
2019-10-30 09:57:14 +01:00
packages.add(tdClz.getPackage());
AccessType[] accessTypes = AccessType.values();
for(AccessType accessType : accessTypes) {
@SuppressWarnings("rawtypes")
Class clz = accessType.getTypeClass();
2020-02-03 10:51:29 +01:00
if(!Type.class.isAssignableFrom(clz)) {
Class<Element> dummyClz = accessType.getDummyImplementationClass();
if(dummyClz != null) {
2020-07-02 18:52:52 +02:00
ElementMapper.registerSubtypes(clz, dummyClz);
}else {
2020-07-02 19:17:57 +02:00
ElementMapper.registerSubtype(clz);
2020-07-02 18:52:52 +02:00
}
2020-02-03 10:51:29 +01:00
packages.add(clz.getPackage());
}
}
2023-02-06 16:35:30 +01:00
Set<AccessType> baseTypes = new LinkedHashSet<>();
baseTypes.add(AccessType.PROPERTY_ELEMENT);
baseTypes.add(AccessType.ENTITY_ELEMENT);
baseTypes.add(AccessType.RELATION_ELEMENT);
2020-07-02 19:17:57 +02:00
2023-02-06 16:35:30 +01:00
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);
}
}
2023-02-06 18:03:47 +01:00
try {
2023-02-08 17:56:13 +01:00
Knowledge.getInstance();
2023-02-06 18:03:47 +01:00
}catch (Exception e) {
throw new RuntimeException(e);
}
2023-02-06 16:35:30 +01:00
}
2020-07-02 18:52:52 +02:00
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);
2020-07-02 19:17:57 +02:00
registerSubtype(implementationClass);
2020-07-02 18:52:52 +02:00
}
2020-07-02 19:17:57 +02:00
public static <El extends Element> void registerSubtype(Class<El> clz) {
2020-07-02 18:52:52 +02:00
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
*/
2021-07-05 15:43:07 +02:00
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
*/
2021-07-05 15:43:07 +02:00
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
*/
2020-02-03 10:51:29 +01:00
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
*/
2020-02-03 10:51:29 +01:00
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
*/
2020-02-03 10:51:29 +01:00
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(Element.SUPERTYPES_PROPERTY);
stringBuffer.append(" property as string array to allow to instantiate the most appropriated class.");
return stringBuffer;
}
protected static JsonNode setAvailableSuperclass(JsonNode jsonNode) {
String unknownType = jsonNode.get(Element.TYPE_PROPERTY).asText();
ArrayNode arrayNode = (ArrayNode) jsonNode.get(Element.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(!jsonNode.has(Element.EXPECTED_TYPE_PROPERTY)) {
((ObjectNode) jsonNode).set(Element.EXPECTED_TYPE_PROPERTY, jsonNode.get(Element.TYPE_PROPERTY));
}
((ObjectNode) jsonNode).set(Element.TYPE_PROPERTY, new TextNode(candidatedSupertype));
((ObjectNode) jsonNode).remove(Element.SUPERTYPES_PROPERTY);
return jsonNode;
}
StringBuffer stringBuffer = getError(unknownType);
logger.trace("Unable to unmarshall {}. {}", jsonNode.toString(), stringBuffer.toString());
throw new RuntimeException(stringBuffer.toString());
}
protected static JsonNode analizeFullJson(JsonNode jsonNode) {
String cls = jsonNode.get(Element.TYPE_PROPERTY).asText();
2021-02-11 17:37:42 +01:00
if(!knownTypes.containsKey(cls)) {
jsonNode = setAvailableSuperclass(jsonNode);
}
Iterator<String> iterator = jsonNode.fieldNames();
while(iterator.hasNext()) {
String fieldName = iterator.next();
JsonNode jn = jsonNode.get(fieldName);
if(jn.getNodeType() == JsonNodeType.OBJECT) {
jn = analizeFullJson(jn);
((ObjectNode) jsonNode).set(fieldName, jn);
}
}
return jsonNode;
}
/**
2021-02-17 16:14:22 +01:00
* Creates a resource of given class from its serialization in a given
* {@link Reader}.
* @param clz the class of the resource
2021-02-17 16:14:22 +01:00
* @param reader the reader
* @return the resource
* @throws JsonParseException
* @throws JsonMappingException
* @throws IOException
*/
2021-02-17 16:14:22 +01:00
public static <El extends Element> El unmarshal(Class<El> clz, Reader reader)
throws JsonParseException, JsonMappingException, IOException {
try {
2021-02-17 16:14:22 +01:00
return mapper.readValue(reader, clz);
} catch (JsonMappingException e) {
2021-02-17 16:14:22 +01:00
JsonNode jsonNode = mapper.readTree(reader);
jsonNode = analizeFullJson(jsonNode);
return ElementMapper.unmarshal(clz, mapper.writeValueAsString(jsonNode));
}
}
/**
* 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 (JsonMappingException e) {
JsonNode jsonNode = mapper.readTree(stream);
jsonNode = analizeFullJson(jsonNode);
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
*/
2021-07-05 15:43:07 +02:00
public static <El extends Element> El unmarshal(Class<El> clz, String string)
throws JsonParseException, JsonMappingException, IOException {
2021-02-17 16:14:22 +01:00
try {
return mapper.readValue(string, clz);
} catch (JsonMappingException e) {
JsonNode jsonNode = mapper.readTree(string);
jsonNode = analizeFullJson(jsonNode);
return ElementMapper.unmarshal(clz, mapper.writeValueAsString(jsonNode));
}
}
2021-07-05 15:43:07 +02:00
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);
2021-02-17 16:14:22 +01:00
try {
return mapper.readValue(string, type);
} catch (JsonMappingException e) {
2021-07-05 15:43:07 +02:00
List<El> ret = new ArrayList<>();
2021-02-17 16:14:22 +01:00
ArrayNode arrayNode = (ArrayNode) mapper.readTree(string);
for(JsonNode jsonNode : arrayNode) {
jsonNode = analizeFullJson(jsonNode);
ret.add(ElementMapper.unmarshal(clz, mapper.writeValueAsString(jsonNode)));
}
return ret;
}
}
2021-07-05 15:43:07 +02:00
public static <El extends Element> List<El> unmarshalList(String string)
throws JsonParseException, JsonMappingException, IOException {
JavaType type = mapper.getTypeFactory().constructCollectionType(ArrayList.class, Element.class);
2021-02-17 16:14:22 +01:00
try {
return mapper.readValue(string, type);
} catch (JsonMappingException e) {
2021-07-05 15:43:07 +02:00
List<El> ret = new ArrayList<>();
2021-02-17 16:14:22 +01:00
ArrayNode arrayNode = (ArrayNode) mapper.readTree(string);
for(JsonNode jsonNode : arrayNode) {
jsonNode = analizeFullJson(jsonNode);
2021-07-05 15:43:07 +02:00
ret.add((El) ElementMapper.unmarshal(Element.class, mapper.writeValueAsString(jsonNode)));
2021-02-17 16:14:22 +01:00
}
return ret;
}
}
}