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

330 lines
11 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.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.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;
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;
/**
* @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);
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);
}else {
ElementMapper.registerSubtype(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> 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, 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);
}
protected static StringBuffer getError(String unknownType) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(unknownType);
stringBuffer.append(" is an unknown type. Please provide ");
stringBuffer.append(Element.SUPERCLASSES_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.CLASS_PROPERTY).asText();
ArrayNode arrayNode = (ArrayNode) jsonNode.get(Element.SUPERCLASSES_PROPERTY);
// TODO save original class and superclasses
String candidatedSuperClass = null;
for(int i = 0; i < arrayNode.size(); i++) {
String superClass = arrayNode.get(i).asText();
if(knownTypes.containsKey(superClass)) {
candidatedSuperClass = superClass;
try {
// Checking if it is one of the base type. In some cases we need to use dummy
// implementation
AccessType accessType = Enum.valueOf(AccessType.class, superClass.toUpperCase());
// 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.
candidatedSuperClass = accessType.getDummyImplementationClass().getSimpleName();
}
} catch(Exception ex) {
// can continue discovery
}
break;
}
}
if(candidatedSuperClass!=null) {
((ObjectNode) jsonNode).set(Element.CLASS_PROPERTY, new TextNode(candidatedSuperClass));
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.CLASS_PROPERTY).asText();
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;
}
/**
* 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 (JsonMappingException e) {
JsonNode jsonNode = mapper.readTree(string);
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
*/
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);
}
}