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 java.util.UUID; import org.gcube.com.fasterxml.jackson.databind.JsonNode; import org.gcube.com.fasterxml.jackson.databind.node.ArrayNode; import org.gcube.informationsystem.base.reference.AccessType; import org.gcube.informationsystem.base.reference.Element; 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.model.reference.relations.Relation; 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.security.SecurityContext; import org.gcube.informationsystem.resourceregistry.contexts.security.SecurityContext.PermissionMode; import org.gcube.informationsystem.resourceregistry.instances.base.ElementManagementUtility; 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.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 { public ResourceManagement() { super(AccessType.RESOURCE); } @Override protected ResourceNotFoundException getSpecificElementNotFoundException(NotFoundException e) { return new ResourceNotFoundException(e.getMessage(), e.getCause()); } @Override public ResourceAvailableInAnotherContextException getSpecificERAvailableInAnotherContextException( String message) { return new ResourceAvailableInAnotherContextException(message); } @Override protected ResourceAlreadyPresentException getSpecificERAlreadyPresentException(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) { @SuppressWarnings("rawtypes") 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 { 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; } @SuppressWarnings("unchecked") @Override protected boolean 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); @SuppressWarnings("rawtypes") 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(); } } element.delete(); return true; } @Override protected Map reallyAddToContext(SecurityContext targetSecurityContext) throws ContextException, ResourceRegistryException { Map affectedInstances = new HashMap<>(); if(!dryRunContextSharing) { 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.setDryRunContextSharing(dryRunContextSharing); relationManagement.setHonourPropagationConstraintsInContextSharing(honourPropagationConstraintsInContextSharing); Map resourceCharacterisationInstances = relationManagement.internalAddToContext(targetSecurityContext); if(relationManagement instanceof ConsistsOfManagement) { facetCounter = facetCounter + resourceCharacterisationInstances.size(); } affectedInstances.putAll(resourceCharacterisationInstances); } 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()); } } return affectedInstances; } public String all(boolean polymorphic) throws ResourceRegistryException { 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(); } } } protected Set getResourceTypeConstraint() throws SchemaException, ResourceRegistryException{ Set constraints = new HashSet<>(); TypesCache typesCache = TypesCache.getInstance(); CachedType cachedType = typesCache.getCachedType(typeName); ResourceType resourceType = (ResourceType) cachedType.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) { CachedType cachedSuperType = 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 typeSatified(TypesCache typesCache, String requiredType, String effectiveType) throws SchemaException, ResourceRegistryException { if(requiredType.compareTo(effectiveType)==0) { return true; } CachedType cachedType = typesCache.getCachedType(requiredType); if(cachedType.getSubTypes().contains(effectiveType)) { return true; } return false; } 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; } @Override public void sanityCheck() throws SchemaViolationException, ResourceRegistryException { JsonNode resourceInstance = createCompleteJsonNode(); TypesCache typesCache = TypesCache.getInstance(); Set consistsOfFacetConstraints = getResourceTypeConstraint(); Map satisfiedConsistsOfFacet = new HashMap<>(); ArrayNode consistsOfArrayNode = (ArrayNode) resourceInstance.get(Resource.CONSISTS_OF_PROPERTY); for(JsonNode consistsOfJsonNode : consistsOfArrayNode) { String consistsOfType = consistsOfJsonNode.get(Element.CLASS_PROPERTY).asText(); JsonNode facetJsonNode = consistsOfJsonNode.get(Relation.TARGET_PROPERTY); String facetType = facetJsonNode.get(Element.CLASS_PROPERTY).asText(); 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); } } } /* private String facetMustBePresentErrorMessage(String consistsOfType, UUID consistsOfUUID, String facetType, UUID facetUUID) { StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append("To avoid to have an incosistent graph, add to context no follows cannot add a "); stringBuffer.append(ConsistsOf.NAME); stringBuffer.append(" relation (i.e. "); stringBuffer.append(consistsOfType); stringBuffer.append(" with UUID "); stringBuffer.append(consistsOfUUID.toString()); stringBuffer.append(") without indicating the target "); stringBuffer.append(Facet.NAME); stringBuffer.append(" (i.e. "); stringBuffer.append(facetType); stringBuffer.append(" with UUID "); stringBuffer.append(facetUUID.toString()); stringBuffer.append(")."); return stringBuffer.toString(); } @Override public void sanityCheck(Map expectedInstances) throws SchemaViolationException, ResourceRegistryException { JsonNode resourceInstance = createCompleteJsonNode(); TypesCache typesCache = TypesCache.getInstance(); Set consistsOfFacetConstraints = getResourceTypeConstraint(); Map satisfiedConsistsOfFacet = new HashMap<>(); boolean oneFacetAvailable = false; ArrayNode consistsOfArrayNode = (ArrayNode) resourceInstance.get(Resource.CONSISTS_OF_PROPERTY); for(JsonNode consistsOfJsonNode : consistsOfArrayNode) { String consistsOfType = consistsOfJsonNode.get(Element.CLASS_PROPERTY).asText(); String consistsOfUUIDString = consistsOfJsonNode.get(IdentifiableElement.HEADER_PROPERTY).get(Header.UUID_PROPERTY).asText(); UUID consistsOfUUID = UUID.fromString(consistsOfUUIDString); JsonNode facetJsonNode = consistsOfJsonNode.get(Relation.TARGET_PROPERTY); String facetType = facetJsonNode.get(Element.CLASS_PROPERTY).asText(); String facetUUIDString = facetJsonNode.get(IdentifiableElement.HEADER_PROPERTY).get(Header.UUID_PROPERTY).asText(); UUID facetUUID = UUID.fromString(facetUUIDString); if(expectedInstances!=null) { // Integrity check for add/remove to/from Context // If the ConsistsOf relation is present also the target facet // must be in the expected instances. if(expectedInstances.containsKey(consistsOfUUID)) { // we need to check that also the facets is present if(!expectedInstances.containsKey(facetUUID)) { String error = facetMustBePresentErrorMessage(consistsOfType, consistsOfUUID, facetType, facetUUID); logger.debug(error); throw new SchemaViolationException(error); } oneFacetAvailable = true; } } for(LinkedEntity constraint : consistsOfFacetConstraints) { // Check min and max 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); } } if(expectedInstances!=null && !oneFacetAvailable) { StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append("To avoid to have an incosistent graph, add to context no follows cannot add a "); stringBuffer.append(Resource.NAME); stringBuffer.append(" (i.e. "); stringBuffer.append(typeName); stringBuffer.append(" with UUID "); stringBuffer.append(uuid.toString()); stringBuffer.append(") without adding at least a "); stringBuffer.append(Facet.NAME); logger.debug(stringBuffer.toString()); throw new SchemaViolationException(stringBuffer.toString()); } } */ }