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> knownTypes; // // protected static final Map, Class> interfaceToImplementation; // protected static final Map> 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 packages = new ArrayList(); /* Class 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 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 regsitrationProviders = ServiceLoader .load(RegistrationProvider.class); for(RegistrationProvider registrationProvider : regsitrationProviders) { registerPackages(registrationProvider.getPackagesToRegister()); } } public static void registerPackages(List 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 void registerSubtypes(Class... classes) { for(Class clz : classes) { String typeName = TypeMapper.getType(clz); knownTypes.put(typeName, clz); // if(clz.isAnnotationPresent(JsonDeserialize.class)) { // JsonDeserialize jsonDeserialize = clz.getAnnotation(JsonDeserialize.class); // Class impl = (Class) 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 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 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 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 String marshal(List 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 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 unmarshal(Class 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 unmarshal(Class 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 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 unmarshal(Class 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 unmarshalWithReader(Class clz, String string) throws JsonParseException, JsonMappingException, IOException { return mapper.readerFor(clz).readValue(string); } public static List unmarshalList(Class clz, String string) throws JsonParseException, JsonMappingException, IOException { JavaType type = mapper.getTypeFactory().constructCollectionType(ArrayList.class, clz); return mapper.readValue(string, type); } public static List unmarshalList(String string) throws JsonParseException, JsonMappingException, IOException { JavaType type = mapper.getTypeFactory().constructCollectionType(ArrayList.class, Element.class); return mapper.readValue(string, type); } }