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> 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 packages = new ArrayList(); Class 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 dummyClz = accessType.getDummyImplementationClass(); if(dummyClz != null) { // ElementMapper.registerSubtypes(clz, dummyClz); ElementMapper.registerSubtype(dummyClz); }else { ElementMapper.registerSubtype(clz); } packages.add(clz.getPackage()); } } Set baseTypes = new LinkedHashSet<>(); baseTypes.add(AccessType.PROPERTY_ELEMENT); baseTypes.add(AccessType.ENTITY_ELEMENT); baseTypes.add(AccessType.RELATION_ELEMENT); for(AccessType accessType : baseTypes) { Class clz = accessType.getTypeClass(); try { Discovery 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 void registerSubtypes(Class clz, Class 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 void registerSubtype(Class 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 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 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 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); } 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 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 unmarshal(Class 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 unmarshal(Class 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 unmarshal(Class 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 List unmarshalList(Class 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 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 List 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 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; } } }