resource-registry/src/main/java/org/gcube/informationsystem/resourceregistry/instances/model/entities/ResourceManagement.java

563 lines
21 KiB
Java

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<Resource, ResourceType> {
/*
* 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<OEdge> 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<OEdge> iterable = element.getEdges(ODirection.OUT);
Iterator<OEdge> 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<OEdge> 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<LinkedEntity> getResourceTypeConstraint() throws SchemaException, ResourceRegistryException{
Set<LinkedEntity> constraints = new HashSet<>();
TypesCache typesCache = TypesCache.getInstance();
ResourceType resourceType = getCachedType().getType();
List<LinkedEntity> linkedEntities = resourceType.getFacets();
if(linkedEntities!=null) {
for(LinkedEntity linkedEntity : linkedEntities) {
if(linkedEntity.getMin()>0 || (linkedEntity.getMax()!=null && linkedEntity.getMax()>0)) {
constraints.add(linkedEntity);
}
}
}
Set<CachedType<ResourceType>> cachedSuperTypes = new HashSet<>();
List<String> superTypes = cachedType.getSuperTypes();
for(String superType : superTypes) {
@SuppressWarnings("unchecked")
CachedType<ResourceType> cachedSuperType = (CachedType<ResourceType>) typesCache.getCachedType(superType);
cachedSuperTypes.add(cachedSuperType);
ResourceType resourceSuperType = (ResourceType) cachedSuperType.getType();
List<LinkedEntity> 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<LinkedEntity> consistsOfFacetConstraints = getResourceTypeConstraint();
Map<LinkedEntity, Integer> satisfiedConsistsOfFacet = new HashMap<>();
Iterable<OEdge> 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(satisfiedTimes<linkedEntity.getMin()) {
String message = constraintNotSatisfiedErrorMessage(linkedEntity, satisfiedTimes);
throw new SchemaViolationException(message);
}
Integer max = linkedEntity.getMax();
if((max!=null && max>0) && satisfiedTimes>max) {
String message = constraintNotSatisfiedErrorMessage(linkedEntity, satisfiedTimes);
throw new SchemaViolationException(message);
}
}
}
}