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.common.authorization.library.provider.CalledMethodProvider; 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.ERElement; import org.gcube.informationsystem.model.reference.properties.Metadata; import org.gcube.informationsystem.model.reference.relations.Relation; 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.ServerContextCache; 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.requests.RequestUtility; import org.gcube.informationsystem.resourceregistry.requests.ServerRequestInfo; import org.gcube.informationsystem.resourceregistry.types.CachedType; import org.gcube.informationsystem.resourceregistry.types.TypesCache; import org.gcube.informationsystem.resourceregistry.utils.MetadataOrient; import org.gcube.informationsystem.resourceregistry.utils.MetadataUtility; import org.gcube.informationsystem.resourceregistry.utils.DBUtility; import org.gcube.informationsystem.resourceregistry.utils.UUIDUtility; 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.informationsystem.types.reference.properties.PropertyType; import org.gcube.informationsystem.utils.TypeUtility; import org.gcube.informationsystem.utils.UUIDManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.arcadedb.database.Document; import com.arcadedb.database.MutableDocument; import com.arcadedb.graph.Vertex.DIRECTION; import com.arcadedb.remote.RemoteDatabase; import com.arcadedb.schema.DocumentType; import com.arcadedb.schema.Property; /** * @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 RemoteDatabase database; protected UUID uuid; protected JsonNode jsonNode; // protected OClass documentType; protected DocumentType documentType; protected String typeName; protected JsonNode self; protected JsonNode complete; protected CachedType cachedType; protected El element; protected boolean reload; /** * Used to force Meta inclusion. * It is use for example to request to include meta * by the context cache manager. */ protected boolean forceIncludeMeta; /** * Used to force Meta inclusion in all instances. * It is use for example to request to include meta * by the context cache manager. */ protected boolean forceIncludeAllMeta; /** * 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.ignoreKeys.add(Element.TYPE_PROPERTY); this.ignoreKeys.add(Element.SUPERTYPES_PROPERTY); this.ignoreKeys.add(Element.EXPECTED_TYPE_PROPERTY); this.ignoreKeys.add(IdentifiableElement.ID_PROPERTY); this.ignoreKeys.add(IdentifiableElement.METADATA_PROPERTY); 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<>(); this.forceIncludeMeta = false; this.forceIncludeAllMeta = false; } public static DIRECTION opposite(DIRECTION d) { switch (d) { case IN: return DIRECTION.OUT; case OUT: return DIRECTION.IN; default: return d; } } public boolean isForceIncludeMeta() { return forceIncludeMeta; } public void setForceIncludeMeta(boolean forceIncludeMeta) { this.forceIncludeMeta = forceIncludeMeta; } public boolean isForceIncludeAllMeta() { return forceIncludeAllMeta; } public void setForceIncludeAllMeta(boolean forceIncludeAllMeta) { this.forceIncludeAllMeta = forceIncludeAllMeta; } public Map getAffectedInstances() { return affectedInstances; } public boolean isDryRun() { return dryRun; } public void setDryRun(boolean dryRun) { this.dryRun = dryRun; } public 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 setDatabase(RemoteDatabase remoteDatabase) { this.database = remoteDatabase; } protected DocumentType getDocumentType() throws SchemaException, ResourceRegistryException { if(documentType == null) { if(element != null) { try { documentType = ElementManagementUtility.getDocumentType(element); if(typeName==null) { typeName = documentType.getName(); } getCachedType().setDocumentType(documentType); }catch (ResourceRegistryException e) { try { documentType = getCachedType().getDocumentType(); if(typeName==null) { typeName = documentType.getName(); } }catch (Exception e1) { throw e; } } } else { if(typeName==null) { throw new SchemaException("Unknown type name. Please set it first."); } documentType = getCachedType().getDocumentType(); AccessType gotAccessType = cachedType.getAccessType(); if(accessType!=gotAccessType) { throw new SchemaException(typeName + " is not a " + accessType.getName()); } } } return documentType; } @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 = getDocumentType().getName(); } if(typeName==null && jsonNode!=null) { this.typeName = TypeUtility.getTypeName(jsonNode); } } return typeName; } protected void checkJsonNode() throws ResourceRegistryException { if(uuid == null) { try { uuid = UUIDUtility.getUUID(jsonNode); } catch(Exception e) { } } else { checkUUIDMatch(); } if(uuid!=null) { try { UUIDManager.getInstance().validateUUID(uuid); } catch (Exception e) { throw new ResourceRegistryException(e.getMessage()); } } if(this.typeName == null) { this.typeName = TypeUtility.getTypeName(jsonNode); getDocumentType(); } else { checkERMatch(); } } protected void checkERMatch() throws ResourceRegistryException { if(jsonNode != null) { String type = TypeUtility.getTypeName(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); } } getDocumentType(); } protected void checkUUIDMatch() throws ResourceRegistryException { UUID resourceUUID = UUIDUtility.getUUID(jsonNode); if(resourceUUID!=null) { if(resourceUUID.compareTo(uuid) != 0) { String error = String.format( "UUID provided in the instance (%s) differs from UUID (%s) used to identify the %s instance", resourceUUID.toString(), uuid.toString(), typeName); throw new ResourceRegistryException(error); } } } private void analizeProperty(Document element, String key, ObjectNode objectNode) throws ResourceRegistryException { Object object = element.get(key); if(object == null) { objectNode.replace(key, null); return; } JsonNode jsonNode = getPropertyForJson(key, object); if(jsonNode != null) { objectNode.replace(key, jsonNode); } } private JsonNode createSelfJsonNode() throws ResourceRegistryException { try { ObjectMapper objectMapper = new ObjectMapper(); ObjectNode objectNode = objectMapper.createObjectNode(); El element = getElement(); Set keys = element.getPropertyNames(); /* Add first these key to provide an order in Json */ ServerRequestInfo requestInfo = RequestUtility.getRequestInfo().get(); List keysToAddFirst = new ArrayList<>(); keysToAddFirst.add(Element.TYPE_PROPERTY); keysToAddFirst.add(Element.SUPERTYPES_PROPERTY); keysToAddFirst.add(IdentifiableElement.ID_PROPERTY); keysToAddFirst.add(IdentifiableElement.METADATA_PROPERTY); keysToAddFirst.add(ERElement.CONTEXTS_PROPERTY); keysToAddFirst.add(Relation.PROPAGATION_CONSTRAINT_PROPERTY); for(String key : keysToAddFirst) { switch (key) { case Element.TYPE_PROPERTY: objectNode.put(Element.TYPE_PROPERTY, getTypeName()); break; case Element.SUPERTYPES_PROPERTY: Collection supertypes = getCachedType().getSuperTypes(); ArrayNode arrayNode = objectMapper.valueToTree(supertypes); objectNode.replace(Element.SUPERTYPES_PROPERTY, arrayNode); break; case IdentifiableElement.METADATA_PROPERTY: if(requestInfo.includeMeta() || forceIncludeMeta) { if(requestInfo.allMeta() || entryPoint || forceIncludeAllMeta) { analizeProperty(element, key, objectNode); } } break; case ERElement.CONTEXTS_PROPERTY: if(requestInfo.includeContexts()) { objectNode.replace(ERElement.CONTEXTS_PROPERTY, getContextsAsObjectNode()); } break; default: if(keys.contains(key)) { analizeProperty(element, key, objectNode); } break; } } for(String key : keys) { if(keysToAddFirst.contains(key)) { // the property has been already added continue; } analizeProperty(element, key, objectNode); } 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); UUIDManager uuidManager = UUIDManager.getInstance(); if(uuid == null) { uuid = uuidManager.generateValidUUID(); } element = reallyCreate(); ((MutableDocument) element).set(IdentifiableElement.ID_PROPERTY, uuid.toString()); MetadataUtility.addMetadata(element); getWorkingContext().addElement(element, database); // 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(); MetadataUtility.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 = UUIDUtility.getUUID(element); DocumentType documentType = getDocumentType(); this.typeName = documentType.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 DBUtility.getElementByUUID(database, 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 DBUtility.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 { database = getWorkingContext().getRemoteDatabase(PermissionMode.READER); setAsEntryPoint(); setOperation(Operation.QUERY); return reallyGetAll(polymorphic); } catch(ResourceRegistryException e) { throw e; } catch(Exception e) { throw new ResourceRegistryException(e); } finally { if(database != null) { database.close(); } // if(current!=null) { // current.activateOnCurrentThread(); // } } } public boolean exists() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException { // ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal(); try { database = getWorkingContext().getRemoteDatabase(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(database != null) { database.close(); } // if(current!=null) { // database.activateOnCurrentThread(); // } } } public String createOrUpdate() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException { // ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal(); try { database = getWorkingContext().getRemoteDatabase(PermissionMode.WRITER); database.begin(); boolean update = false; try { setAsEntryPoint(); getElement(); update = true; internalUpdate(); } catch(NotFoundException e) { setAsEntryPoint(); String calledMethod = CalledMethodProvider.instance.get(); calledMethod = calledMethod.replace("update", "create"); CalledMethodProvider.instance.set(calledMethod); internalCreate(); } database.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(database != null) { database.rollback(); } throw e; } catch(Exception e) { logger.error("Unable to update {} with UUID {}", accessType.getName(), uuid, e); if(database != null) { database.rollback(); } throw new ResourceRegistryException(e); } finally { if(database != null) { database.close(); } // if(current!=null) { // current.activateOnCurrentThread(); // } } } public String create() throws AlreadyPresentException, ResourceRegistryException { // ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal(); try { database = getWorkingContext().getRemoteDatabase(PermissionMode.WRITER); database.begin(); setAsEntryPoint(); internalCreate(); database.commit(); // TODO Notify to subscriptionNotification return serializeAsJsonNode().toString(); } catch(ResourceRegistryException e) { logger.error("Unable to create {}", accessType.getName()); if(database != null) { database.rollback(); } throw e; } catch(Exception e) { logger.error("Unable to create {}", accessType.getName(), e); if(database != null) { database.rollback(); } throw new ResourceRegistryException(e); } finally { if(database != null) { database.close(); } // if(current!=null) { // current.activateOnCurrentThread(); // } } } public String read() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException { // ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal(); try { database = getWorkingContext().getRemoteDatabase(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(database != null) { database.close(); } // if(current!=null) { // current.activateOnCurrentThread(); // } } } public String update() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException { // ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal(); try { database = getWorkingContext().getRemoteDatabase(PermissionMode.WRITER); database.begin(); setAsEntryPoint(); internalUpdate(); database.commit(); setReload(true); // TODO Notify to subscriptionNotification return serializeAsJsonNode().toString(); } catch(ResourceRegistryException e) { logger.error("Unable to update {} with UUID {}", accessType.getName(), uuid); if(database != null) { database.rollback(); } throw e; } catch(Exception e) { logger.error("Unable to update {} with UUID {}", accessType.getName(), uuid, e); if(database != null) { database.rollback(); } throw new ResourceRegistryException(e); } finally { if(database != null) { database.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); database = getWorkingContext().getRemoteDatabase(PermissionMode.WRITER); database.begin(); setAsEntryPoint(); internalDelete(); if(!dryRun) { database.commit(); logger.info("{} with UUID {} was successfully deleted.", accessType.getName(), uuid); }else { database.rollback(); } } catch(ResourceRegistryException e) { logger.error("Unable to delete {} with UUID {}", accessType.getName(), uuid); if(database != null) { database.rollback(); } throw e; } catch(Exception e) { logger.error("Unable to delete {} with UUID {}", accessType.getName(), uuid, e); if(database != null) { database.rollback(); } throw new ResourceRegistryException(e); } finally { if(database != null) { database.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(); database = adminSecurityContext.getRemoteDatabase(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(database != null) { database.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(); ServerContextCache contextCache = ServerContextCache.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 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(Property property, String key, JsonNode value) throws Exception { switch (property.getType()) { case EMBEDDED: Document document = PropertyElementManagement.getPropertyDocument(value); ((MutableDocument) element).set(key, document, com.arcadedb.schema.Type.EMBEDDED); break; case LIST: List list = new ArrayList(); Iterator arrayElement = value.elements(); while(arrayElement.hasNext()) { JsonNode elementOfArray = arrayElement.next(); Object object = null; if(property.getType()!=null) { object = getObjectFromJsonNode(elementOfArray); }else { object = PropertyElementManagement.getPropertyDocument(elementOfArray); } list.add(object); } ((MutableDocument) element).set(key, list, com.arcadedb.schema.Type.LIST); break; case MAP: Map map = new HashMap<>(); Iterator fieldNames = value.fieldNames(); while(fieldNames.hasNext()) { String fieldKey = fieldNames.next(); Object object = null; if(property.getType()!=null) { object = getObjectFromJsonNode(value.get(fieldKey)); }else { object = PropertyElementManagement.getPropertyDocument(value.get(fieldKey)); } map.put(fieldKey, object); } ((MutableDocument) element).set(key, map, com.arcadedb.schema.Type.MAP); break; case STRING: if(value.getNodeType() == JsonNodeType.OBJECT) { ((MutableDocument) element).set(key, value.toString()); }else { ((MutableDocument) element).set(key, getObjectFromJsonNode(value)); } break; default: Object obj = getObjectFromJsonNode(value); if(obj != null) { ((MutableDocument) element).set(key, obj); } break; } } public Document updateProperties(DocumentType type, Document 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()); getDocumentType(); for(String key : properties.keySet()) { try { JsonNode value = properties.get(key); Property property = type.getProperty(key); if(property==null) { Object object = getObjectFromJsonNode(value); if(object != null) { if(object instanceof Document) { ((MutableDocument) element).set(key, object, com.arcadedb.schema.Type.EMBEDDED); /* * Due to bug https://github.com/orientechnologies/orientdb/issues/7354 * we should not support ArrayList */ } else if(object instanceof List){ ((MutableDocument) element).set(key, object, com.arcadedb.schema.Type.LIST); } else { ((MutableDocument) element).set(key, object); } } }else { setProperty(property, 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; } } ((MutableDocument) element).remove(key); } return element; } public static boolean isUserAllowedToGetPrivacyMeta() { // TODO return true; } 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.METADATA_PROPERTY) == 0) { // Keeping the metadata MetadataOrient metadataOrient = MetadataUtility.getMetadataOrient((Document) object); ObjectNode metadataJson = (ObjectNode) DBUtility.toJsonNode(metadataOrient); if(!isUserAllowedToGetPrivacyMeta()) { metadataJson.replace(Metadata.CREATED_BY_PROPERTY, new TextNode(Metadata.HIDDEN_FOR_PRIVACY_USER)); metadataJson.replace(Metadata.LAST_UPDATE_BY_PROPERTY, new TextNode(Metadata.HIDDEN_FOR_PRIVACY_USER)); } // TODO check a solution for supertypes TypesCache typesCache = TypesCache.getInstance(); @SuppressWarnings("unchecked") CachedType> metadataType = (CachedType>) typesCache.getCachedType(Metadata.NAME); ObjectMapper objectMapper = new ObjectMapper(); Collection superClasses = metadataType.getSuperTypes(); ArrayNode arrayNode = objectMapper.valueToTree(superClasses); metadataJson.replace(Element.SUPERTYPES_PROPERTY, arrayNode); return metadataJson; } if(key.compareTo(IdentifiableElement.ID_PROPERTY) == 0 ) { return new TextNode(object.toString()); } if(ignoreKeys.contains(key)) { return null; } for(String prefix : ignoreStartWithKeys) { if(key.startsWith(prefix)) { return null; } } if(object instanceof Document) { Document document = (Document) object; return PropertyElementManagement.getJsonNode(document); } if(object instanceof Date) { Property property = getDocumentType().getProperty(key); com.arcadedb.schema.Type type = property.getType(); DateFormat dateFormat = ODateHelper.getDateTimeFormatInstance(); switch(type) { 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 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 ((MutableDocument) element).set(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. } } }