package org.gcube.informationsystem.resourceregistry.instances.model.entities; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.gcube.com.fasterxml.jackson.databind.JsonNode; import org.gcube.informationsystem.base.reference.AccessType; import org.gcube.informationsystem.context.reference.entities.Context; import org.gcube.informationsystem.model.reference.entities.Facet; import org.gcube.informationsystem.model.reference.entities.Resource; import org.gcube.informationsystem.model.reference.relations.ConsistsOf; import org.gcube.informationsystem.model.reference.relations.IsRelatedTo; 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.context.ContextException; import org.gcube.informationsystem.resourceregistry.api.exceptions.entity.resource.ResourceAlreadyPresentException; import org.gcube.informationsystem.resourceregistry.api.exceptions.entity.resource.ResourceAvailableInAnotherContextException; import org.gcube.informationsystem.resourceregistry.api.exceptions.entity.resource.ResourceNotFoundException; import org.gcube.informationsystem.resourceregistry.api.exceptions.schema.SchemaException; import org.gcube.informationsystem.resourceregistry.api.exceptions.schema.SchemaViolationException; import org.gcube.informationsystem.resourceregistry.contexts.ContextUtility; import org.gcube.informationsystem.resourceregistry.contexts.security.SecurityContext.PermissionMode; import org.gcube.informationsystem.resourceregistry.instances.base.ElementManagementUtility; import org.gcube.informationsystem.resourceregistry.instances.model.Operation; import org.gcube.informationsystem.resourceregistry.instances.model.relations.ConsistsOfManagement; import org.gcube.informationsystem.resourceregistry.instances.model.relations.IsRelatedToManagement; import org.gcube.informationsystem.resourceregistry.instances.model.relations.RelationManagement; import org.gcube.informationsystem.resourceregistry.types.CachedType; import org.gcube.informationsystem.resourceregistry.types.TypesCache; import org.gcube.informationsystem.resourceregistry.utils.Utility; import org.gcube.informationsystem.types.reference.entities.ResourceType; import org.gcube.informationsystem.types.reference.properties.LinkedEntity; import com.orientechnologies.orient.core.db.document.ODatabaseDocument; import com.orientechnologies.orient.core.metadata.schema.OClass; import com.orientechnologies.orient.core.record.ODirection; import com.orientechnologies.orient.core.record.OEdge; import com.orientechnologies.orient.core.record.OVertex; /** * @author Luca Frosini (ISTI - CNR) */ public class ResourceManagement extends EntityManagement { /* * In case of a resource is deleted due to cascade effect the sanity check is not required. * The Resource and all its facets are deleted. */ private boolean sanityCheckNotRequired; public ResourceManagement() { super(AccessType.RESOURCE); this.sanityCheckNotRequired = false; } public void setSanityCheckNotRequired() { this.sanityCheckNotRequired = true; } @Override protected ResourceNotFoundException getSpecificNotFoundException(NotFoundException e) { return new ResourceNotFoundException(e.getMessage(), e.getCause()); } @Override public ResourceAvailableInAnotherContextException getSpecificAvailableInAnotherContextException( String message) { return new ResourceAvailableInAnotherContextException(message); } @Override protected ResourceAlreadyPresentException getSpecificAlreadyPresentException(String message) { return new ResourceAlreadyPresentException(message); } @Override public JsonNode createCompleteJsonNode() throws ResourceRegistryException { JsonNode sourceResource = serializeSelfAsJsonNode(); /* * Cannot get ConsistsOf edge only because is not polymorphic for a * com.tinkerpop.blueprints.Vertex vertex.getEdges(Direction.OUT, * ConsistsOf.NAME); TODO Looks for a different query */ Iterable edges = getElement().getEdges(ODirection.OUT); for(OEdge edge : edges) { RelationManagement relationManagement = getRelationManagement(edge); relationManagement.setReload(reload); if(relationManagement.giveMeSourceEntityManagementAsIs() == null) { relationManagement.setSourceEntityManagement(this); } if(relationManagement.giveMeSourceEntityManagementAsIs() != this) { StringBuilder errorMessage = new StringBuilder(); errorMessage.append("SourceEntityManagement for "); errorMessage.append(relationManagement.getClass().getSimpleName()); errorMessage.append(" is not the one expected. "); errorMessage.append(Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); throw new ResourceRegistryException(errorMessage.toString()); } if(relationManagement instanceof ConsistsOfManagement) { try { relationManagement.includeSource(false); JsonNode consistsOf = relationManagement.serializeAsJsonNode(); sourceResource = addConsistsOf(sourceResource, consistsOf); } catch(ResourceRegistryException e) { logger.error("Unable to correctly serialize {}. {}", edge, Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); throw e; } catch(Exception e) { logger.error("Unable to correctly serialize {}. {}", edge, Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); throw new ResourceRegistryException(e); } } /* * This comment is just to show that IsRelatedTo is not serialized by default as * design choice and not because forget * * else if(orientEdgeType.isSubClassOf(IsRelatedTo.NAME)){ JsonNode * isRelatedTo = relationManagement.serializeAsJson(true, true); sourceResource * = addIsRelatedTo(sourceResource, isRelatedTo); } */ } return sourceResource; } public static JsonNode addConsistsOf(JsonNode sourceResource, JsonNode consistsOf) throws ResourceRegistryException { return addRelation(sourceResource, consistsOf, AccessType.CONSISTS_OF.lowerCaseFirstCharacter()); } public static JsonNode addIsRelatedTo(JsonNode sourceResource, JsonNode isRelatedTo) throws ResourceRegistryException { return addRelation(sourceResource, isRelatedTo, AccessType.IS_RELATED_TO.lowerCaseFirstCharacter()); } @Override protected OVertex reallyCreate() throws ResourceAlreadyPresentException, ResourceRegistryException { createVertex(); String property = AccessType.CONSISTS_OF.lowerCaseFirstCharacter(); if(jsonNode.has(property)) { JsonNode jsonNodeArray = jsonNode.get(property); for(JsonNode consistOfJsonNode : jsonNodeArray) { ConsistsOfManagement com = new ConsistsOfManagement(); com.setWorkingContext(getWorkingContext()); com.setODatabaseDocument(oDatabaseDocument); com.setJsonNode(consistOfJsonNode); com.setSourceEntityManagement(this); com.internalCreate(); addToRelationManagement(com); } } property = AccessType.IS_RELATED_TO.lowerCaseFirstCharacter(); if(jsonNode.has(property)) { JsonNode jsonNodeArray = jsonNode.get(property); for(JsonNode relationJsonNode : jsonNodeArray) { IsRelatedToManagement irtm = new IsRelatedToManagement(); irtm.setWorkingContext(getWorkingContext()); irtm.setODatabaseDocument(oDatabaseDocument); irtm.setJsonNode(relationJsonNode); irtm.setSourceEntityManagement(this); irtm.internalCreate(); addToRelationManagement(irtm); } } return element; } @Override protected OVertex reallyUpdate() throws ResourceNotFoundException, ResourceRegistryException { getElement(); String property = AccessType.CONSISTS_OF.lowerCaseFirstCharacter(); if(jsonNode.has(property)) { JsonNode jsonNodeArray = jsonNode.get(property); for(JsonNode relationJsonNode : jsonNodeArray) { ConsistsOfManagement com = new ConsistsOfManagement(); com.setWorkingContext(getWorkingContext()); com.setODatabaseDocument(oDatabaseDocument); com.setJsonNode(relationJsonNode); com.internalCreateOrUdate(); addToRelationManagement(com); } } property = AccessType.IS_RELATED_TO.lowerCaseFirstCharacter(); if(jsonNode.has(property)) { JsonNode jsonNodeArray = jsonNode.get(property); for(JsonNode relationJsonNode : jsonNodeArray) { IsRelatedToManagement irtm = new IsRelatedToManagement(); irtm.setWorkingContext(getWorkingContext()); irtm.setODatabaseDocument(oDatabaseDocument); irtm.setJsonNode(relationJsonNode); irtm.internalUpdate(); addToRelationManagement(irtm); } } return element; } @Override protected void reallyDelete() throws ResourceNotFoundException, ResourceRegistryException { // internalDeleteResource(orientGraph, uuid, null); getElement(); Iterable iterable = element.getEdges(ODirection.OUT); Iterator iterator = iterable.iterator(); while(iterator.hasNext()) { OEdge edge = iterator.next(); OClass oClass = ElementManagementUtility.getOClass(edge); RelationManagement relationManagement = null; if(oClass.isSubClassOf(IsRelatedTo.NAME)) { relationManagement = new IsRelatedToManagement(); } else if(oClass.isSubClassOf(ConsistsOf.NAME)) { relationManagement = new ConsistsOfManagement(); } else { logger.warn("{} is not a {} nor a {}. {}", Utility.toJsonString(edge, true), IsRelatedTo.NAME, ConsistsOf.NAME, Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); } if(relationManagement != null) { relationManagement.setWorkingContext(getWorkingContext()); relationManagement.setODatabaseDocument(oDatabaseDocument); relationManagement.setElement(edge); relationManagement.internalDelete(); affectedInstances.putAll(relationManagement.getAffectedInstances()); } } iterable = element.getEdges(ODirection.OUT); iterator = iterable.iterator(); while(iterator.hasNext()) { // in relations are affected because is the system which ensure this integrity } affectedInstances.put(uuid, serializeAsAffectedInstance()); element.delete(); } @Override protected void reallyAddToContext() throws ContextException, ResourceRegistryException { if(!sourceSecurityContext.isElementInContext(getElement())) { // The element in not in the source security context. It will be skipped skipped = true; return; } targetSecurityContext.addElement(getElement(), oDatabaseDocument); /* * DO NOT UNCOMMENT * // affectedInstances.put(uuid, serializeSelfOnly()); * the instance is added in internalAddToContext() function after * the update of Header metadata i.e. modifiedBy, lastUpdateTime */ if(honourPropagationConstraintsInContextSharing) { Iterable edges = getElement().getEdges(ODirection.OUT); int facetCounter = 0; for(OEdge edge : edges) { RelationManagement relationManagement = getRelationManagement(edge); relationManagement.setDryRun(dryRun); relationManagement.setHonourPropagationConstraintsInContextSharing(honourPropagationConstraintsInContextSharing); relationManagement.setSourceSecurityContext(sourceSecurityContext); relationManagement.setTargetSecurityContext(targetSecurityContext); relationManagement.internalAddToContext(); affectedInstances.putAll(relationManagement.getAffectedInstances()); if(relationManagement instanceof ConsistsOfManagement) { facetCounter = facetCounter + relationManagement.getAffectedInstances().size(); } relationManagement.sanityCheck(); } if(facetCounter == 0) { StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append("Any "); stringBuffer.append(Resource.NAME); stringBuffer.append(" must "); stringBuffer.append(ConsistsOf.NAME); stringBuffer.append(" at least of one "); stringBuffer.append(Facet.NAME); stringBuffer.append(" in any "); stringBuffer.append(Context.NAME); throw new ResourceRegistryException(stringBuffer.toString()); } } } @Override public String all(boolean polymorphic) throws ResourceRegistryException { ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal(); try { oDatabaseDocument = getWorkingContext().getDatabaseDocument(PermissionMode.READER); 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(); } } } protected Set getResourceTypeConstraint() throws SchemaException, ResourceRegistryException{ Set constraints = new HashSet<>(); TypesCache typesCache = TypesCache.getInstance(); ResourceType resourceType = getCachedType().getType(); List linkedEntities = resourceType.getFacets(); if(linkedEntities!=null) { for(LinkedEntity linkedEntity : linkedEntities) { if(linkedEntity.getMin()>0 || (linkedEntity.getMax()!=null && linkedEntity.getMax()>0)) { constraints.add(linkedEntity); } } } Set> cachedSuperTypes = new HashSet<>(); List superTypes = cachedType.getSuperTypes(); for(String superType : superTypes) { @SuppressWarnings("unchecked") CachedType cachedSuperType = (CachedType) typesCache.getCachedType(superType); cachedSuperTypes.add(cachedSuperType); ResourceType resourceSuperType = (ResourceType) cachedSuperType.getType(); List linkedEnt = resourceSuperType.getFacets(); if(linkedEnt!=null) { for(LinkedEntity linkedEntity : linkedEnt) { if(linkedEntity.getMin()>0 || (linkedEntity.getMax()!=null && linkedEntity.getMax()>0)) { constraints.add(linkedEntity); } } } } return constraints; } private String constraintNotSatisfiedErrorMessage(LinkedEntity linkedEntity, Integer occurrence) { StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append("A "); stringBuffer.append(typeName); stringBuffer.append(" must be described by "); stringBuffer.append(linkedEntity.getRelation()); stringBuffer.append(" -> "); stringBuffer.append(linkedEntity.getTarget()); stringBuffer.append(" with the following constraint: max:"); stringBuffer.append(linkedEntity.getMax()); stringBuffer.append(", min:"); stringBuffer.append(linkedEntity.getMin()); stringBuffer.append(". Found "); stringBuffer.append(occurrence); stringBuffer.append(" instances"); stringBuffer.append(". The constraint has been defined by "); stringBuffer.append(linkedEntity.getSource()); stringBuffer.append(" type."); return stringBuffer.toString(); } protected boolean constraintSatisfied(TypesCache typesCache, LinkedEntity constraint, String consistsOfType, String facetType) throws SchemaException, ResourceRegistryException { String requiredSourceResourceType = constraint.getSource(); if(!typeSatified(typesCache, requiredSourceResourceType, typeName)) { return false; } String requiredConsistsOfType = constraint.getRelation(); if(!typeSatified(typesCache, requiredConsistsOfType, consistsOfType)) { return false; } String requiredTargetFacetType = constraint.getTarget(); if(!typeSatified(typesCache, requiredTargetFacetType, facetType)) { return false; } return true; } /** * The default sanity check is not valid for resources which do not have properties * and instead must be validated in terms of facets. * The Resource Header is managed with dedicated code for all instaces. */ @Override public void sanityCheck() throws SchemaViolationException, ResourceRegistryException { if(operation.isSafe()) { /* * The sanity check is not required for a safe operation. */ return; } // In case of a resource is deleted due to cascade effect is look like is the entry point // of the operation and the sanity check is not required. The Resource and all its facets are deleted. if( sanityCheckNotRequired || (entryPoint && (operation == Operation.DELETE || operation == Operation.REMOVE_FROM_CONTEXT) ) ) { return; } TypesCache typesCache = TypesCache.getInstance(); Set consistsOfFacetConstraints = getResourceTypeConstraint(); Map satisfiedConsistsOfFacet = new HashMap<>(); Iterable edges = getElement().getEdges(ODirection.OUT); for(OEdge edge : edges) { String id = edge.getIdentity().toString(); RelationManagement relationManagement = relationManagements.get(id); if(entryPoint) { switch (operation) { case ADD_TO_CONTEXT: if(relationManagement == null) { try { relationManagement = ElementManagementUtility.getRelationManagement(targetSecurityContext, oDatabaseDocument, edge); relationManagement.setSourceSecurityContext(sourceSecurityContext); relationManagements.put(id, relationManagement); }catch (AvailableInAnotherContextException e) { continue; } }else { /* * The relation has been already managed in add to context so it has been added to the targetSecurityContext. * The transaction is not yet committed so we need to consider as part of context without trying to load from * DB with dedicated security connection. */ } break; case REMOVE_FROM_CONTEXT: if(relationManagement != null) { /* * The relation has been removed from the targetContext but it has not been committed. * We must consider as not available in targetSecurityContext */ continue; }else { relationManagement = ElementManagementUtility.getRelationManagement(targetSecurityContext, oDatabaseDocument, edge); relationManagements.put(id, relationManagement); } break; case CREATE: case UPDATE: if(relationManagement == null) { relationManagement = ElementManagementUtility.getRelationManagement(getWorkingContext(), oDatabaseDocument, edge); relationManagements.put(id, relationManagement); /* * Here the AvailableInAnotherContextException should not occur because the connection to the DB is with the * context role and not with admin as in addToContext/removeFromContext */ }else { /* * During create and update the relation could be still not be in the context because the * transaction is still to be committed. We use the pending instance of relation management * to evaluate the instance. */ } break; default: break; } }else { if(operation == Operation.REMOVE_FROM_CONTEXT && relationManagement!=null) { /* * The relation has been removed from the targetContext but it has not been committed. * We must consider as not available in targetSecurityContext */ continue; } if(relationManagement==null) { relationManagement = getRelationManagement(edge); relationManagements.put(id, relationManagement); } } if(!(relationManagement instanceof ConsistsOfManagement)) { continue; } String consistsOfType = relationManagement.getTypeName(); String facetType = relationManagement.getTargetEntityManagement().getTypeName(); for(LinkedEntity constraint : consistsOfFacetConstraints) { if(constraintSatisfied(typesCache, constraint, consistsOfType, facetType)) { Integer integer = satisfiedConsistsOfFacet.get(constraint); if(integer==null) { satisfiedConsistsOfFacet.put(constraint, 1); }else { satisfiedConsistsOfFacet.put(constraint, ++integer); } } } } consistsOfFacetConstraints.removeAll(satisfiedConsistsOfFacet.keySet()); if(!consistsOfFacetConstraints.isEmpty()) { String message = constraintNotSatisfiedErrorMessage(consistsOfFacetConstraints.iterator().next(), 0); throw new SchemaViolationException(message); } for(LinkedEntity linkedEntity : satisfiedConsistsOfFacet.keySet()) { Integer satisfiedTimes = satisfiedConsistsOfFacet.get(linkedEntity); if(satisfiedTimes0) && satisfiedTimes>max) { String message = constraintNotSatisfiedErrorMessage(linkedEntity, satisfiedTimes); throw new SchemaViolationException(message); } } } }