package org.gcube.informationsystem.resourceregistry.instances.base; import java.io.IOException; import java.text.DateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.UUID; import javax.activation.UnsupportedDataTypeException; import org.gcube.com.fasterxml.jackson.core.JsonProcessingException; import org.gcube.com.fasterxml.jackson.databind.JsonNode; import org.gcube.com.fasterxml.jackson.databind.ObjectMapper; import org.gcube.com.fasterxml.jackson.databind.node.ArrayNode; import org.gcube.com.fasterxml.jackson.databind.node.BooleanNode; 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.base.reference.IdentifiableElement; import org.gcube.informationsystem.model.reference.properties.Header; import org.gcube.informationsystem.resourceregistry.api.contexts.ContextCache; import org.gcube.informationsystem.resourceregistry.api.exceptions.AlreadyPresentException; import org.gcube.informationsystem.resourceregistry.api.exceptions.AvailableInAnotherContextException; import org.gcube.informationsystem.resourceregistry.api.exceptions.NotFoundException; import org.gcube.informationsystem.resourceregistry.api.exceptions.ResourceRegistryException; import org.gcube.informationsystem.resourceregistry.api.exceptions.contexts.ContextException; import org.gcube.informationsystem.resourceregistry.api.exceptions.types.SchemaException; import org.gcube.informationsystem.resourceregistry.api.exceptions.types.SchemaViolationException; import org.gcube.informationsystem.resourceregistry.contexts.ContextUtility; import org.gcube.informationsystem.resourceregistry.contexts.security.AdminSecurityContext; import org.gcube.informationsystem.resourceregistry.contexts.security.SecurityContext; import org.gcube.informationsystem.resourceregistry.contexts.security.SecurityContext.PermissionMode; import org.gcube.informationsystem.resourceregistry.instances.base.properties.PropertyElementManagement; import org.gcube.informationsystem.resourceregistry.instances.model.Operation; import org.gcube.informationsystem.resourceregistry.types.CachedType; import org.gcube.informationsystem.resourceregistry.types.TypesCache; import org.gcube.informationsystem.resourceregistry.utils.HeaderOrient; import org.gcube.informationsystem.resourceregistry.utils.HeaderUtility; import org.gcube.informationsystem.resourceregistry.utils.Utility; import org.gcube.informationsystem.types.reference.Type; import org.gcube.informationsystem.types.reference.entities.ResourceType; import org.gcube.informationsystem.types.reference.properties.PropertyDefinition; import org.gcube.smartgears.utils.InnerMethodName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.orientechnologies.orient.core.db.document.ODatabaseDocument; import com.orientechnologies.orient.core.metadata.schema.OClass; import com.orientechnologies.orient.core.metadata.schema.OProperty; import com.orientechnologies.orient.core.metadata.schema.OType; import com.orientechnologies.orient.core.record.OElement; import com.orientechnologies.orient.core.record.impl.ODocument; import com.orientechnologies.orient.core.util.ODateHelper; /** * @author Luca Frosini (ISTI - CNR) */ public abstract class ElementManagement { protected Logger logger = LoggerFactory.getLogger(this.getClass()); public final static String DELETED = "deleted"; public final static String AT = "@"; public final static String UNDERSCORE = "_"; protected final Set ignoreKeys; protected final Set ignoreStartWithKeys; protected Class elementClass; protected final AccessType accessType; protected ODatabaseDocument oDatabaseDocument; protected UUID uuid; protected JsonNode jsonNode; protected OClass oClass; protected String typeName; protected JsonNode self; protected JsonNode complete; protected CachedType cachedType; protected El element; protected boolean reload; /** * Some operation, e.g. delete has a cascade impact which is not know a priori by the client. * Setting this variable to false the system just simulate the operation so an interested client * could know the impact in advance. * * By the default the system execute effectively the requested operation. * So this variable is initialised as false. * */ protected boolean dryRun; /** * An operation can affects multiple instances (e.g. create, update) * We need to know if the instance is the entry point of the operation * or if it is just a subordinated operation. * This is required for example in delete to trigger sanity check * in the Resource when the action is performed directly on the Facet. * Resource sanity check is not required to be triggered by the Facet * because the entry point of the action already take care * of triggering this action (e.g. the delete was invoked on the Resource and * each describing Facets must not trigger multiple time the sanityCheck). */ protected boolean entryPoint; /** * It is assigned only in the entry point */ protected Operation operation; /** * A Delete operation has a cascade impact we could want to know the impact * Instances affected by a delete */ protected final Map affectedInstances; protected ElementManagement(AccessType accessType) { this.accessType = accessType; this.ignoreKeys = new HashSet(); this.ignoreStartWithKeys = new HashSet(); this.ignoreStartWithKeys.add(ElementManagement.AT); this.ignoreStartWithKeys.add(ElementManagement.UNDERSCORE); this.reload = false; this.entryPoint = false; this.operation = null; /* * By the default the system execute the operation * which has a cascade impact so this variable is initialised as false. */ this.dryRun = false; this.affectedInstances = new HashMap<>(); } public Map getAffectedInstances() { return affectedInstances; } public boolean isDryRun() { return dryRun; } public void setDryRun(boolean dryRun) { this.dryRun = dryRun; } protected void setAsEntryPoint() { this.entryPoint = true; } public void setOperation(Operation operation) { this.operation = operation; } protected void cleanCachedSerialization() { this.self = null; this.complete = null; } public UUID getUUID() { return uuid; } public boolean isReload() { return reload; } public void setReload(boolean reload) { this.reload = reload; } public AccessType getAccessType() { return accessType; } protected SecurityContext workingContext; protected SecurityContext getWorkingContext() throws ResourceRegistryException { if(workingContext == null) { workingContext = ContextUtility.getCurrentSecurityContext(); } return workingContext; } public void setWorkingContext(SecurityContext workingContext) { this.workingContext = workingContext; } public void setUUID(UUID uuid) throws ResourceRegistryException { this.uuid = uuid; if(jsonNode != null) { checkUUIDMatch(); } } public void setJsonNode(JsonNode jsonNode) throws ResourceRegistryException { this.jsonNode = jsonNode; checkJsonNode(); } public void setJson(String json) throws ResourceRegistryException { ObjectMapper mapper = new ObjectMapper(); try { this.jsonNode = mapper.readTree(json); } catch(IOException e) { throw new ResourceRegistryException(e); } checkJsonNode(); } public void setODatabaseDocument(ODatabaseDocument oDatabaseDocument) { this.oDatabaseDocument = oDatabaseDocument; } public void setOClass(OClass oClass) { this.oClass = oClass; } protected OClass getOClass() throws SchemaException, ResourceRegistryException { if(oClass == null) { if(element != null) { try { oClass = ElementManagementUtility.getOClass(element); if(typeName==null) { typeName = oClass.getName(); } getCachedType().setOClass(oClass); }catch (ResourceRegistryException e) { try { oClass = getCachedType().getOClass(); if(typeName==null) { typeName = oClass.getName(); } }catch (Exception e1) { throw e; } } } else { if(typeName==null) { throw new SchemaException("Unknown type name. Please set it first."); } oClass = getCachedType().getOClass(); AccessType gotAccessType = cachedType.getAccessType(); if(accessType!=gotAccessType) { throw new SchemaException(typeName + " is not a " + accessType.getName()); } } } return oClass; } @SuppressWarnings("unchecked") protected CachedType getCachedType(){ if(cachedType==null) { TypesCache typesCache = TypesCache.getInstance(); cachedType = (CachedType) typesCache.getCachedType(typeName); } return cachedType; } public void setElementType(String elementType) throws ResourceRegistryException { if(this.typeName == null) { if(elementType == null || elementType.compareTo("") == 0) { elementType = accessType.getName(); } this.typeName = elementType; } else { if(elementType.compareTo(elementType) != 0) { throw new ResourceRegistryException( "Provided type " + elementType + " does not match with the one already known " + this.accessType); } } if(jsonNode != null) { checkERMatch(); } } public String getTypeName() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException { if(typeName==null) { if(element!=null) { typeName = element.getProperty(Element.CLASS_PROPERTY); } if(typeName==null && jsonNode!=null) { this.typeName = getClassProperty(jsonNode); } if(typeName==null) { typeName = getOClass().getName(); } } return typeName; } protected void checkJsonNode() throws ResourceRegistryException { if(uuid == null) { try { uuid = org.gcube.informationsystem.utils.Utility.getUUIDFromJsonNode(jsonNode); } catch(Exception e) { } } else { checkUUIDMatch(); } if(this.typeName == null) { this.typeName = getClassProperty(jsonNode); getOClass(); } else { checkERMatch(); } } protected void checkERMatch() throws ResourceRegistryException { if(jsonNode != null) { String type = getClassProperty(jsonNode); if(type != null && type.compareTo(typeName) != 0) { String error = String.format("Requested type does not match with json representation %s!=%s", typeName, type); logger.trace(error); throw new ResourceRegistryException(error); } } getOClass(); } protected void checkUUIDMatch() throws ResourceRegistryException { Header header = null; try { header = HeaderUtility.getHeader(jsonNode, false); } catch(Exception e) { throw new ResourceRegistryException(e); } if(header != null) { UUID resourceUUID = header.getUUID(); if(resourceUUID.compareTo(uuid) != 0) { String error = String.format( "UUID provided in header (%s) differs from the one (%s) used to identify the %s instance", resourceUUID.toString(), uuid.toString(), typeName); throw new ResourceRegistryException(error); } } } private JsonNode createSelfJsonNode() throws ResourceRegistryException { try { ObjectMapper objectMapper = new ObjectMapper(); ObjectNode objectNode = objectMapper.createObjectNode(); OElement element = getElement(); Set keys = element.getPropertyNames(); for(String key : keys) { Object object = element.getProperty(key); if(object == null) { objectNode.replace(key, null); continue; } JsonNode jsonNode = getPropertyForJson(key, object); if(jsonNode != null) { objectNode.replace(key, jsonNode); } } objectNode.put(Element.CLASS_PROPERTY, getTypeName()); Collection superClasses = getCachedType().getSuperTypes(); ArrayNode arrayNode = objectMapper.valueToTree(superClasses); objectNode.replace(Element.SUPERCLASSES_PROPERTY, arrayNode); return objectNode; } catch(ResourceRegistryException e) { throw e; } catch(Exception e) { throw new ResourceRegistryException("Error while serializing " + getElement().toString(), e); } } public JsonNode serializeAsAffectedInstance() throws ResourceRegistryException { return serializeSelfAsJsonNode(); } public JsonNode serializeSelfAsJsonNode() throws ResourceRegistryException { try { if(self==null || reload) { self = createSelfJsonNode(); } return self.deepCopy(); } catch(Exception e) { throw new ResourceRegistryException(e); } } protected abstract JsonNode createCompleteJsonNode() throws ResourceRegistryException; public JsonNode serializeAsJsonNode() throws ResourceRegistryException { try { if(complete==null || reload) { complete = createCompleteJsonNode(); } return complete; } catch(Exception e) { throw new ResourceRegistryException(e); } } protected abstract El reallyCreate() throws AlreadyPresentException, ResourceRegistryException; public El internalCreate() throws AlreadyPresentException, ResourceRegistryException { try { setOperation(Operation.CREATE); reallyCreate(); Header entityHeader = HeaderUtility.getHeader(jsonNode, true); if(entityHeader != null) { element.setProperty(IdentifiableElement.HEADER_PROPERTY, entityHeader); } else { entityHeader = HeaderUtility.addHeader(element, null); uuid = entityHeader.getUUID(); } getWorkingContext().addElement(element, oDatabaseDocument); element.save(); sanityCheck(); return element; } catch(ResourceRegistryException e) { throw e; } catch(Exception e) { throw new ResourceRegistryException("Error Creating " + typeName + " with " + jsonNode, e); } } protected abstract El reallyUpdate() throws NotFoundException, ResourceRegistryException; public El internalUpdate() throws NotFoundException, ResourceRegistryException { try { setOperation(Operation.UPDATE); reallyUpdate(); HeaderUtility.updateModifiedByAndLastUpdate(element); element.save(); sanityCheck(); return element; } catch(ResourceRegistryException e) { throw e; } catch(Exception e) { throw new ResourceRegistryException("Error Updating " + typeName + " with " + jsonNode, e); } } public El internalCreateOrUdate() throws ResourceRegistryException { try { return internalUpdate(); } catch(NotFoundException e) { return internalCreate(); } } protected abstract void reallyDelete() throws NotFoundException, ResourceRegistryException; public void internalDelete() throws NotFoundException, ResourceRegistryException { setOperation(Operation.DELETE); reallyDelete(); sanityCheck(); } public void setElement(El element) throws ResourceRegistryException { if(element == null) { throw new ResourceRegistryException("Trying to set null " + elementClass.getSimpleName() + " in " + this); } this.element = element; this.uuid = HeaderUtility.getHeader(element).getUUID(); OClass oClass = getOClass(); this.typeName = oClass.getName(); } protected abstract NotFoundException getSpecificNotFoundException(NotFoundException e); protected abstract AlreadyPresentException getSpecificAlreadyPresentException(String message); public El getElement() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException { if(element == null) { try { element = retrieveElement(); } catch(NotFoundException e) { throw e; } catch(ResourceRegistryException e) { throw e; } catch(Exception e) { throw new ResourceRegistryException(e); } } else { if(reload) { element.reload(); } } return element; } public El retrieveElement() throws NotFoundException, ResourceRegistryException { try { if(uuid == null) { throw new NotFoundException("null UUID does not allow to retrieve the Element"); } return Utility.getElementByUUID(oDatabaseDocument, typeName == null ? accessType.getName() : typeName, uuid, elementClass); } catch(NotFoundException e) { throw getSpecificNotFoundException(e); } catch(ResourceRegistryException e) { throw e; } catch(Exception e) { throw new ResourceRegistryException(e); } } public El retrieveElementFromAnyContext() throws NotFoundException, ResourceRegistryException { try { return Utility.getElementByUUIDAsAdmin(typeName == null ? accessType.getName() : typeName, uuid, elementClass); } catch(NotFoundException e) { throw getSpecificNotFoundException(e); } catch(ResourceRegistryException e) { throw e; } catch(Exception e) { throw new ResourceRegistryException(e); } } public abstract String reallyGetAll(boolean polymorphic) throws ResourceRegistryException; public String all(boolean polymorphic) throws ResourceRegistryException { ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal(); try { oDatabaseDocument = getWorkingContext().getDatabaseDocument(PermissionMode.READER); setAsEntryPoint(); setOperation(Operation.QUERY); return reallyGetAll(polymorphic); } catch(ResourceRegistryException e) { throw e; } catch(Exception e) { throw new ResourceRegistryException(e); } finally { if(oDatabaseDocument != null) { oDatabaseDocument.close(); } if(current!=null) { current.activateOnCurrentThread(); } } } public boolean exists() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException { ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal(); try { oDatabaseDocument = getWorkingContext().getDatabaseDocument(PermissionMode.READER); setAsEntryPoint(); setOperation(Operation.EXISTS); getElement(); return true; } catch(ResourceRegistryException e) { logger.error("Unable to find {} with UUID {}", accessType.getName(), uuid); throw e; } catch(Exception e) { logger.error("Unable to find {} with UUID {}", accessType.getName(), uuid, e); throw new ResourceRegistryException(e); } finally { if(oDatabaseDocument != null) { oDatabaseDocument.close(); } if(current!=null) { current.activateOnCurrentThread(); } } } public String createOrUpdate() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException { ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal(); try { oDatabaseDocument = getWorkingContext().getDatabaseDocument(PermissionMode.WRITER); oDatabaseDocument.begin(); boolean update = false; try { setAsEntryPoint(); getElement(); update = true; internalUpdate(); } catch(NotFoundException e) { setAsEntryPoint(); String calledMethod = InnerMethodName.instance.get(); calledMethod = calledMethod.replace("update", "create"); InnerMethodName.instance.set(calledMethod); internalCreate(); } oDatabaseDocument.commit(); if(update) { setReload(true); } // TODO Notify to subscriptionNotification return serializeAsJsonNode().toString(); } catch(ResourceRegistryException e) { logger.error("Unable to update {} with UUID {}", accessType.getName(), uuid); if(oDatabaseDocument != null) { oDatabaseDocument.rollback(); } throw e; } catch(Exception e) { logger.error("Unable to update {} with UUID {}", accessType.getName(), uuid, e); if(oDatabaseDocument != null) { oDatabaseDocument.rollback(); } throw new ResourceRegistryException(e); } finally { if(oDatabaseDocument != null) { oDatabaseDocument.close(); } if(current!=null) { current.activateOnCurrentThread(); } } } public String create() throws AlreadyPresentException, ResourceRegistryException { ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal(); try { oDatabaseDocument = getWorkingContext().getDatabaseDocument(PermissionMode.WRITER); oDatabaseDocument.begin(); setAsEntryPoint(); internalCreate(); oDatabaseDocument.commit(); // TODO Notify to subscriptionNotification return serializeAsJsonNode().toString(); } catch(ResourceRegistryException e) { logger.error("Unable to create {}", accessType.getName()); if(oDatabaseDocument != null) { oDatabaseDocument.rollback(); } throw e; } catch(Exception e) { logger.error("Unable to create {}", accessType.getName(), e); if(oDatabaseDocument != null) { oDatabaseDocument.rollback(); } throw new ResourceRegistryException(e); } finally { if(oDatabaseDocument != null) { oDatabaseDocument.close(); } if(current!=null) { current.activateOnCurrentThread(); } } } public String read() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException { ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal(); try { oDatabaseDocument = getWorkingContext().getDatabaseDocument(PermissionMode.READER); setAsEntryPoint(); setOperation(Operation.READ); getElement(); return serializeAsJsonNode().toString(); } catch(ResourceRegistryException e) { logger.error("Unable to read {} with UUID {}", accessType.getName(), uuid); throw e; } catch(Exception e) { logger.error("Unable to read {} with UUID {}", accessType.getName(), uuid, e); throw new ResourceRegistryException(e); } finally { if(oDatabaseDocument != null) { oDatabaseDocument.close(); } if(current!=null) { current.activateOnCurrentThread(); } } } public String update() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException { ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal(); try { oDatabaseDocument = getWorkingContext().getDatabaseDocument(PermissionMode.WRITER); oDatabaseDocument.begin(); setAsEntryPoint(); internalUpdate(); oDatabaseDocument.commit(); setReload(true); // TODO Notify to subscriptionNotification return serializeAsJsonNode().toString(); } catch(ResourceRegistryException e) { logger.error("Unable to update {} with UUID {}", accessType.getName(), uuid); if(oDatabaseDocument != null) { oDatabaseDocument.rollback(); } throw e; } catch(Exception e) { logger.error("Unable to update {} with UUID {}", accessType.getName(), uuid, e); if(oDatabaseDocument != null) { oDatabaseDocument.rollback(); } throw new ResourceRegistryException(e); } finally { if(oDatabaseDocument != null) { oDatabaseDocument.close(); } if(current!=null) { current.activateOnCurrentThread(); } } } public void delete() throws NotFoundException, AvailableInAnotherContextException, SchemaViolationException, ResourceRegistryException { logger.trace("Going to delete {} instance with UUID {}", accessType.getName(), uuid); ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal(); try { // oDatabaseDocument = ContextUtility.getAdminSecurityContext().getDatabaseDocument(PermissionMode.WRITER); oDatabaseDocument = getWorkingContext().getDatabaseDocument(PermissionMode.WRITER); oDatabaseDocument.begin(); setAsEntryPoint(); internalDelete(); if(!dryRun) { oDatabaseDocument.commit(); logger.info("{} with UUID {} was successfully deleted.", accessType.getName(), uuid); }else { oDatabaseDocument.rollback(); } } catch(ResourceRegistryException e) { logger.error("Unable to delete {} with UUID {}", accessType.getName(), uuid); if(oDatabaseDocument != null) { oDatabaseDocument.rollback(); } throw e; } catch(Exception e) { logger.error("Unable to delete {} with UUID {}", accessType.getName(), uuid, e); if(oDatabaseDocument != null) { oDatabaseDocument.rollback(); } throw new ResourceRegistryException(e); } finally { if(oDatabaseDocument != null) { oDatabaseDocument.close(); } if(current!=null) { current.activateOnCurrentThread(); } } } public Set getContextsSet() throws NotFoundException, ContextException, ResourceRegistryException { logger.trace("Going to get contexts for {} instance with UUID {}", typeName, uuid); ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal(); try { AdminSecurityContext adminSecurityContext = ContextUtility.getAdminSecurityContext(); oDatabaseDocument = adminSecurityContext.getDatabaseDocument(PermissionMode.READER); setAsEntryPoint(); setOperation(Operation.GET_METADATA); Set contexts = SecurityContext.getContexts(getElement()); return contexts; } catch(ResourceRegistryException e) { logger.error("Unable to get contexts for {} with UUID {}", typeName, uuid, e); throw e; } catch(Exception e) { logger.error("Unable to get contexts for {} with UUID {}", typeName, uuid, e); throw new ContextException(e); } finally { if(oDatabaseDocument != null) { oDatabaseDocument.close(); } if(current!=null) { current.activateOnCurrentThread(); } } } public String getContexts() throws NotFoundException, ContextException, ResourceRegistryException { try { ObjectMapper objectMapper = new ObjectMapper(); ObjectNode objectNode = getContextsAsObjectNode(objectMapper); return objectMapper.writeValueAsString(objectNode); } catch(ResourceRegistryException e) { throw e; } catch (Exception e) { throw new ContextException(e); } } private ObjectNode getContextsAsObjectNode(ObjectMapper objectMapper) throws NotFoundException, ContextException, ResourceRegistryException { try { Set contexts = getContextsSet(); ContextCache contextCache = ContextCache.getInstance(); ObjectNode objectNode = objectMapper.createObjectNode(); for(String contextUUID : contexts) { String contextFullName = contextCache.getContextFullNameByUUID(contextUUID); objectNode.put(contextUUID, contextFullName); } return objectNode; } catch(ResourceRegistryException e) { throw e; } catch (Exception e) { throw new ContextException(e); } } public ObjectNode getContextsAsObjectNode() throws NotFoundException, ContextException, ResourceRegistryException { ObjectMapper objectMapper = new ObjectMapper(); return getContextsAsObjectNode(objectMapper); } public static String getClassProperty(JsonNode jsonNode) { if(jsonNode.has(Element.CLASS_PROPERTY)) { return jsonNode.get(Element.CLASS_PROPERTY).asText(); } return null; } public static Object getObjectFromJsonNode(JsonNode value) throws UnsupportedDataTypeException, ResourceRegistryException { JsonNodeType jsonNodeType = value.getNodeType(); switch(jsonNodeType) { case OBJECT: return PropertyElementManagement.getPropertyDocument(value); case ARRAY: /* * Due to bug https://github.com/orientechnologies/orientdb/issues/7354 * we should not support ArrayList */ List list = new ArrayList<>(); ArrayNode arrayNode = (ArrayNode) value; for(JsonNode node : arrayNode) { list.add(getObjectFromJsonNode(node)); } return list; case BINARY: break; case BOOLEAN: return value.asBoolean(); case NULL: break; case NUMBER: if(value.isDouble() || value.isFloat()) { return value.asDouble(); } if(value.isBigInteger() || value.isShort() || value.isInt()) { return value.asInt(); } if(value.isLong()) { return value.asLong(); } break; case STRING: return value.asText(); case MISSING: break; case POJO: break; default: break; } return null; } public Map getPropertyMap(JsonNode jsonNode, Set ignoreKeys, Set ignoreStartWith) throws JsonProcessingException, IOException { Map map = new HashMap<>(); if(ignoreKeys == null) { ignoreKeys = new HashSet<>(); } if(ignoreStartWith == null) { ignoreStartWith = new HashSet<>(); } Iterator> fields = jsonNode.fields(); OUTER_WHILE: while(fields.hasNext()) { Entry entry = fields.next(); String key = entry.getKey(); if(ignoreKeys.contains(key)) { continue; } for(String prefix : ignoreStartWith) { if(key.startsWith(prefix)) { continue OUTER_WHILE; } } JsonNode value = entry.getValue(); map.put(key, value); } return map; } public void setProperty(OProperty oProperty, String key, JsonNode value) throws Exception { switch (oProperty.getType()) { case EMBEDDED: ODocument oDocument = PropertyElementManagement.getPropertyDocument(value); element.setProperty(key, oDocument, OType.EMBEDDED); break; case EMBEDDEDLIST: List list = new ArrayList(); Iterator arrayElement = value.elements(); while(arrayElement.hasNext()) { JsonNode elementOfArray = arrayElement.next(); Object object = null; if(oProperty.getLinkedType()!=null) { object = getObjectFromJsonNode(elementOfArray); }else { object = PropertyElementManagement.getPropertyDocument(elementOfArray); } list.add(object); } element.setProperty(key, list, OType.EMBEDDEDLIST); break; case EMBEDDEDSET: Set set = new HashSet(); Iterator setElement = value.elements(); while(setElement.hasNext()) { JsonNode elementOfSet = setElement.next(); Object object = null; if(oProperty.getLinkedType()!=null) { object = getObjectFromJsonNode(elementOfSet); }else { object = PropertyElementManagement.getPropertyDocument(elementOfSet); } set.add(object); } element.setProperty(key, set, OType.EMBEDDEDSET); break; case EMBEDDEDMAP: Map map = new HashMap<>(); Iterator fieldNames = value.fieldNames(); while(fieldNames.hasNext()) { String fieldKey = fieldNames.next(); Object object = null; if(oProperty.getLinkedType()!=null) { object = getObjectFromJsonNode(value.get(fieldKey)); }else { object = PropertyElementManagement.getPropertyDocument(value.get(fieldKey)); } map.put(fieldKey, object); } element.setProperty(key, map, OType.EMBEDDEDMAP); break; case STRING: if(value.getNodeType() == JsonNodeType.OBJECT) { element.setProperty(key, value.toString()); }else { element.setProperty(key, getObjectFromJsonNode(value)); } break; default: Object obj = getObjectFromJsonNode(value); if(obj != null) { element.setProperty(key, obj); } break; } } public OElement updateProperties(OClass oClass, OElement element, JsonNode jsonNode, Set ignoreKeys, Set ignoreStartWithKeys) throws ResourceRegistryException { Set oldKeys = element.getPropertyNames(); Map properties; try { properties = getPropertyMap(jsonNode, ignoreKeys, ignoreStartWithKeys); } catch(IOException e) { throw new ResourceRegistryException(e); } oldKeys.removeAll(properties.keySet()); getOClass(); for(String key : properties.keySet()) { try { JsonNode value = properties.get(key); OProperty oProperty = oClass.getProperty(key); if(oProperty==null) { Object object = getObjectFromJsonNode(value); if(object != null) { if(object instanceof ODocument) { element.setProperty(key, object, OType.EMBEDDED); /* * Due to bug https://github.com/orientechnologies/orientdb/issues/7354 * we should not support ArrayList */ } else if(object instanceof List){ element.setProperty(key, object, OType.EMBEDDEDLIST); } else { element.setProperty(key, object); } } }else { setProperty(oProperty, key, value); } } catch(Exception e) { String error = String.format("Error while setting property %s : %s (%s)", key, properties.get(key).toString(), e.getMessage()); logger.error(error); throw new ResourceRegistryException(error, e); } } OUTER_FOR: for(String key : oldKeys) { if(ignoreKeys.contains(key)) { continue; } for(String prefix : ignoreStartWithKeys) { if(key.startsWith(prefix)) { continue OUTER_FOR; } } element.removeProperty(key); } element.save(); return element; } protected JsonNode getPropertyForJson(String key, Object object) throws ResourceRegistryException { try { if(object == null) { return null; } if(object instanceof JsonNode) { return (JsonNode) object; } if(key.compareTo(IdentifiableElement.HEADER_PROPERTY) == 0) { // Keeping the header HeaderOrient headerOrient = HeaderUtility.getHeaderOrient((ODocument) object); JsonNode headerJson = Utility.toJsonNode(headerOrient, false); if(ContextUtility.getIncludeInstanceContexts().get()) { ((ObjectNode) headerJson).set(Header.__CONTEXTS, getContextsAsObjectNode()); } return headerJson; } if(ignoreKeys.contains(key)) { return null; } for(String prefix : ignoreStartWithKeys) { if(key.startsWith(prefix)) { return null; } } if(object instanceof ODocument) { ODocument oDocument = (ODocument) object; return PropertyElementManagement.getJsonNode(oDocument); } if(object instanceof Date) { OProperty oProperty = getOClass().getProperty(key); OType oType = oProperty.getType(); DateFormat dateFormat = ODateHelper.getDateTimeFormatInstance(); switch(oType) { case DATE: dateFormat = ODateHelper.getDateFormatInstance(); break; case DATETIME: dateFormat = ODateHelper.getDateTimeFormatInstance(); break; default: break; } return new TextNode(dateFormat.format((Date) object)); } if(object instanceof Collection) { Collection collection = (Collection) object; ObjectMapper objectMapper = new ObjectMapper(); ArrayNode arrayNode = objectMapper.createArrayNode(); for(Object o : collection) { JsonNode obj = getPropertyForJson("PLACEHOLDER", o); if(obj!=null) { arrayNode.add(obj); } } return arrayNode; } if(object instanceof Map) { @SuppressWarnings("unchecked") Map map = (Map) object; ObjectMapper objectMapper = new ObjectMapper(); ObjectNode objectNode = objectMapper.createObjectNode(); for(String k : map.keySet()) { JsonNode obj = getPropertyForJson("PLACEHOLDER", map.get(k)); objectNode.set(k, obj); } return objectNode; } if(object instanceof Boolean) { return BooleanNode.valueOf((Boolean) object); } return new TextNode(object.toString()); } catch(Exception e) { throw new ResourceRegistryException( "Error while serializing " + key + "=" + object.toString() + " in " + getElement().toString(), e); } } /* protected List getSuperclasses() throws SchemaException, ResourceRegistryException { List superClasses = new ArrayList<>(); List allSuperClasses = getOClass().getSuperClasses(); while(allSuperClasses.size()>0) { List toBeAnalysed = new ArrayList<>(allSuperClasses); allSuperClasses = new ArrayList<>(); for(OClass oSuperClass : toBeAnalysed) { String name = oSuperClass.getName(); if(name.compareTo(StringFactory.V.toUpperCase()) == 0 || name.compareTo(StringFactory.E.toUpperCase()) == 0 || name.compareTo(DatabaseEnvironment.O_RESTRICTED_CLASS) == 0) { continue; } if(superClassesToBeExcluded.contains(name)) { continue; } superClasses.add(superClasses.size(), name); allSuperClasses.addAll(oSuperClass.getSuperClasses()); } } return superClasses; } */ protected String getNotNullErrorMessage(String fieldName) { StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append("The type "); stringBuffer.append(typeName); stringBuffer.append(" defines the fields "); stringBuffer.append(fieldName); stringBuffer.append(" as not nullable. Null or no value has been provided instead."); return stringBuffer.toString(); } protected String getMandatoryErrorMessage(String fieldName) { StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append("The type "); stringBuffer.append(typeName); stringBuffer.append(" defines the fields "); stringBuffer.append(fieldName); stringBuffer.append(" as mandatory but no value has been provided."); return stringBuffer.toString(); } protected boolean typeSatified(TypesCache typesCache, String requiredType, String effectiveType) throws SchemaException, ResourceRegistryException { if(requiredType.compareTo(effectiveType)==0) { return true; } @SuppressWarnings("unchecked") CachedType cachedType = (CachedType) typesCache.getCachedType(requiredType); if(cachedType.getSubTypes().contains(effectiveType)) { return true; } return false; } /* * Get not only the properties defined in the type but also the properties * defined in the super types */ protected Set getAllProperties() throws SchemaException, ResourceRegistryException{ TypesCache typesCache = TypesCache.getInstance(); Set definedProperties = getCachedType().getType().getProperties(); Set> cachedSuperTypes = new HashSet<>(); List superTypes = cachedType.getSuperTypes(); for(String superTypeName : superTypes) { @SuppressWarnings("unchecked") CachedType cachedSuperType = (CachedType) typesCache.getCachedType(superTypeName); cachedSuperTypes.add(cachedSuperType); Type superType = cachedSuperType.getType(); Set properties = superType.getProperties(); if(properties!=null) { definedProperties.addAll(properties); } } return definedProperties; } public void sanityCheck() throws SchemaViolationException, ResourceRegistryException { // OrientDB distributed mode does not support // mandatory and notnull constraints due to technical problem // https://www.orientdb.com/docs/last/java/Graph-Schema-Property.html#using-constraints // Going to validate them here if(operation.isSafe()) { /* * The sanity check is not required for a safe operation. */ return; } Set definedProperties = getAllProperties(); if(definedProperties==null) { // The type could define no property return; } Set elementPropertyNames = getElement().getPropertyNames(); for(PropertyDefinition propertyDefinition : definedProperties) { String fieldName = propertyDefinition.getName(); if(propertyDefinition.isMandatory() && !elementPropertyNames.contains(fieldName)) { if(propertyDefinition.isNotnull()) { // If the field is mandatory but null value is accepted I add the // field as null value element.setProperty(fieldName, null); } else { throw new SchemaViolationException(getMandatoryErrorMessage(fieldName)); } } /* JsonNode jsonNode = instances.get(fieldName); if(!propertyDefinition.isNotnull() && jsonNode==null) { throw new SchemaViolationException(getNotNullErrorMessage(fieldName)); } */ // This validation was required to check if all mandatory fields are presents // The validation of the values has been performed at create/update time. } } }