package org.gcube.informationsystem.resourceregistry.instances.model.relations; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.UUID; 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.ObjectNode; import org.gcube.informationsystem.base.reference.AccessType; import org.gcube.informationsystem.model.reference.entities.Entity; import org.gcube.informationsystem.model.reference.entities.Resource; import org.gcube.informationsystem.model.reference.properties.PropagationConstraint; import org.gcube.informationsystem.model.reference.properties.PropagationConstraint.AddConstraint; import org.gcube.informationsystem.model.reference.properties.PropagationConstraint.RemoveConstraint; 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.relation.RelationNotFoundException; import org.gcube.informationsystem.resourceregistry.contexts.ContextUtility; import org.gcube.informationsystem.resourceregistry.contexts.security.SecurityContext; import org.gcube.informationsystem.resourceregistry.contexts.security.SecurityContext.PermissionMode; import org.gcube.informationsystem.resourceregistry.instances.base.ElementManagement; import org.gcube.informationsystem.resourceregistry.instances.base.ElementManagementUtility; import org.gcube.informationsystem.resourceregistry.instances.base.relations.RelationElementManagement; import org.gcube.informationsystem.resourceregistry.instances.model.entities.EntityManagement; import org.gcube.informationsystem.resourceregistry.instances.model.entities.FacetManagement; import org.gcube.informationsystem.resourceregistry.instances.model.entities.ResourceManagement; import org.gcube.informationsystem.resourceregistry.utils.PropagationConstraintOrient; import org.gcube.informationsystem.resourceregistry.utils.Utility; import org.gcube.informationsystem.utils.ElementMapper; import com.orientechnologies.orient.core.db.document.ODatabaseDocument; import com.orientechnologies.orient.core.metadata.schema.OType; import com.orientechnologies.orient.core.record.ODirection; import com.orientechnologies.orient.core.record.OEdge; import com.orientechnologies.orient.core.record.OVertex; import com.orientechnologies.orient.core.record.impl.ODocument; /** * @author Luca Frosini (ISTI - CNR) */ public abstract class RelationManagement> extends RelationElementManagement{ /** * Used in AddToContext. The Relation must add/check also the source entity */ public boolean checkContextOfSourceEntity; public void setCheckContextOfSourceEntity(boolean checkContextOfSourceEntity) { this.checkContextOfSourceEntity = checkContextOfSourceEntity; } public final PropagationConstraint defaultPropagationConstraint; protected RelationManagement(AccessType accessType, Class targetEntityClass, PropagationConstraint defaultPropagationConstraint) { super(accessType, Resource.class, targetEntityClass); this.defaultPropagationConstraint = defaultPropagationConstraint; this.checkContextOfSourceEntity = false; } protected RelationManagement(AccessType accessType, Class targetEntityClass, SecurityContext workingContext, ODatabaseDocument orientGraph, PropagationConstraint defaultPropagationConstraint) { this(accessType, targetEntityClass, defaultPropagationConstraint); this.oDatabaseDocument = orientGraph; setWorkingContext(workingContext); } /* * Needed for ResourceManagement.serializeAsJson() function to check that * sourceEntityManagement is the same of the instance is creating this * RelationManagement. TODO Look for a workaround */ public ResourceManagement giveMeSourceEntityManagementAsIs() throws ResourceRegistryException { return sourceEntityManagement; } public ResourceManagement getSourceEntityManagement() throws ResourceRegistryException { if(sourceEntityManagement == null) { OVertex source = getElement().getVertex(ODirection.OUT); sourceEntityManagement = newSourceEntityManagement(); sourceEntityManagement.setElement(source); } sourceEntityManagement.setReload(reload); return sourceEntityManagement; } public T getTargetEntityManagement() throws ResourceRegistryException { if(targetEntityManagement == null) { OVertex target = getElement().getVertex(ODirection.IN); targetEntityManagement = newTargetEntityManagement(); targetEntityManagement.setElement(target); } targetEntityManagement.setReload(reload); return targetEntityManagement; } public void setSourceEntityManagement(ResourceManagement resourceManagement) { this.sourceEntityManagement = resourceManagement; } public void setTargetEntityManagement(T targetEntityManagement) { this.targetEntityManagement = targetEntityManagement; } @Override public String serialize() throws ResourceRegistryException { return serializeAsJson().toString(); } @Override public JsonNode serializeAsJson() throws ResourceRegistryException { return serializeAsJson(true, true); } public JsonNode serializeAsJson(boolean includeSource, boolean includeTarget) throws ResourceRegistryException { JsonNode relation = serializeSelfOnly(); try { if(includeSource) { EntityManagement sourceEntityManagement = getSourceEntityManagement(); ((ObjectNode) relation).replace(Relation.SOURCE_PROPERTY, sourceEntityManagement.serializeSelfOnly()); } if(includeTarget) { EntityManagement targetEntityManagement = getTargetEntityManagement(); ((ObjectNode) relation).replace(Relation.TARGET_PROPERTY, targetEntityManagement.serializeAsJson()); } } catch(ResourceRegistryException e) { logger.error("Unable to correctly serialize {}. {}", element, Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE, e); throw e; } catch(Exception e) { logger.error("Unable to correctly serialize {}. {}", element, Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE, e); throw new ResourceRegistryException(e); } return relation; } protected Map fullSerialize(Map visitedSourceResources) throws ResourceRegistryException { OVertex source = getElement().getVertex(ODirection.OUT); String id = source.getIdentity().toString(); JsonNode sourceResource = visitedSourceResources.get(id); ResourceManagement resourceManagement = null; if(sourceResource == null) { resourceManagement = (ResourceManagement) ElementManagementUtility.getEntityManagement(getWorkingContext(), oDatabaseDocument, source); if(this instanceof IsRelatedToManagement) { sourceResource = resourceManagement.serializeAsJson(); } else if(this instanceof ConsistsOfManagement) { sourceResource = resourceManagement.serializeSelfOnly(); } else { String error = String.format("{%s is not a %s nor a %s. %s", this, IsRelatedToManagement.class.getSimpleName(), ConsistsOfManagement.class.getSimpleName(), Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); throw new ResourceRegistryException(error); } } if(this instanceof IsRelatedToManagement) { sourceResource = ResourceManagement.addIsRelatedTo(sourceResource, serializeAsJson()); } else if(this instanceof ConsistsOfManagement) { sourceResource = ResourceManagement.addConsistsOf(sourceResource, serializeAsJson()); } else { String error = String.format("{%s is not a %s nor a %s. %s", this, IsRelatedToManagement.class.getSimpleName(), ConsistsOfManagement.class.getSimpleName(), Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); throw new ResourceRegistryException(error); } visitedSourceResources.put(id, sourceResource); return visitedSourceResources; } protected PropagationConstraintOrient getPropagationConstraint(ODocument oDocument) throws ResourceRegistryException { PropagationConstraintOrient propagationConstraintOrient = new PropagationConstraintOrient(); PropagationConstraint propagationConstraint = null; if(oDocument == null) { propagationConstraint = defaultPropagationConstraint; } else if(oDocument instanceof PropagationConstraintOrient) { propagationConstraint = (PropagationConstraint) oDocument; } else { try { propagationConstraint = ElementMapper.unmarshal(PropagationConstraint.class, oDocument.toJSON()); } catch(Exception e) { logger.warn("Unable to recreate {}. {}", PropagationConstraint.NAME, Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); } } AddConstraint addConstraint = propagationConstraint.getAddConstraint(); if(addConstraint == null) { addConstraint = defaultPropagationConstraint.getAddConstraint(); logger.warn("Unable to get {}. Default value ({}) will be used", AddConstraint.class.getSimpleName(), addConstraint); } propagationConstraintOrient.setAddConstraint(addConstraint); RemoveConstraint removeConstraint = propagationConstraint.getRemoveConstraint(); if(removeConstraint == null) { removeConstraint = defaultPropagationConstraint.getRemoveConstraint(); logger.warn("Unable to get {}. Default value ({}) will be used", RemoveConstraint.class.getSimpleName(), removeConstraint); } propagationConstraintOrient.setRemoveConstraint(removeConstraint); return propagationConstraintOrient; } protected void checkPropagationConstraint() throws ResourceRegistryException { Object object = getElement().getProperty(Relation.PROPAGATION_CONSTRAINT_PROPERTY); PropagationConstraintOrient pc = getPropagationConstraint((ODocument) object); getElement().setProperty(Relation.PROPAGATION_CONSTRAINT_PROPERTY, pc, OType.EMBEDDED); } @Override protected OEdge reallyCreate() throws ResourceRegistryException { element = super.reallyCreate(); checkPropagationConstraint(); logger.info("{} successfully created", elementType); return element; } protected ResourceManagement newSourceEntityManagement() throws ResourceRegistryException { return new ResourceManagement(getWorkingContext(), oDatabaseDocument); } protected abstract T newTargetEntityManagement() throws ResourceRegistryException; @Override protected OEdge reallyUpdate() throws ResourceRegistryException { logger.debug("Trying to update {} : {}", elementType, jsonNode); OEdge edge = getElement(); ElementManagement.updateProperties(oClass, edge, jsonNode, ignoreKeys, ignoreStartWithKeys); if(accessType.compareTo(AccessType.CONSISTS_OF) == 0) { JsonNode target = jsonNode.get(Relation.TARGET_PROPERTY); if(target != null) { FacetManagement fm = new FacetManagement(getWorkingContext(), oDatabaseDocument); fm.setJsonNode(target); fm.internalUpdate(); } } logger.info("{} {} successfully updated", elementType, jsonNode); return edge; } @Override protected Map reallyAddToContext(SecurityContext targetSecurityContext) throws ContextException, ResourceRegistryException { getElement(); Map affectedInstances = new HashMap<>(); AddConstraint addConstraint = AddConstraint.unpropagate; try { PropagationConstraint propagationConstraint = Utility.getPropertyDocument(PropagationConstraint.class, element, Relation.PROPAGATION_CONSTRAINT_PROPERTY); if(propagationConstraint.getAddConstraint() != null) { addConstraint = propagationConstraint.getAddConstraint(); } else { String error = String.format("%s.%s in %s is null. %s", Relation.PROPAGATION_CONSTRAINT_PROPERTY, PropagationConstraint.ADD_PROPERTY, Utility.toJsonString(element, true), Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); logger.error(error); throw new ResourceRegistryException(error); } } catch(Exception e) { String error = String.format("Error while getting %s from %s while performing AddToContext. %s", Relation.PROPAGATION_CONSTRAINT_PROPERTY, Utility.toJsonString(element, true), Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); logger.warn(error); throw new ResourceRegistryException(error, e); } switch(addConstraint) { case propagate: /* * The relation must be added only in the case the target vertex must be added. * Otherwise we have a relation which point to an entity outside of the context. */ affectedInstances.putAll(getTargetEntityManagement().internalAddToContext(targetSecurityContext)); targetSecurityContext.addElement(getElement(), oDatabaseDocument); /* * DO NOT UNCOMMENT * the instance is added internalAddToContext() function after * the update of Header metadata i.e. modifiedBy, lastUpdateTime * affectedInstances.put(uuid, serializeSelfOnly()); */ break; case unpropagate: break; default: break; } return affectedInstances; } public Map forcedAddToContext(SecurityContext targetSecurityContext) throws ContextException, ResourceRegistryException { getElement(); Map affectedInstances = new HashMap<>(); /* Adding source to Context */ affectedInstances.putAll(getSourceEntityManagement().internalAddToContext(targetSecurityContext)); /* Adding target to Context */ affectedInstances.putAll(getTargetEntityManagement().internalAddToContext(targetSecurityContext)); targetSecurityContext.addElement(getElement(), oDatabaseDocument); affectedInstances.put(uuid, serializeSelfOnly()); return affectedInstances; } @Override protected Map reallyRemoveFromContext(SecurityContext targetSecurityContext) throws ContextException, ResourceRegistryException { getElement(); Map affectedInstances = new HashMap<>(); RemoveConstraint removeConstraint = RemoveConstraint.keep; try { PropagationConstraint propagationConstraint = Utility.getPropertyDocument(PropagationConstraint.class, element, Relation.PROPAGATION_CONSTRAINT_PROPERTY); if(propagationConstraint.getRemoveConstraint() != null) { removeConstraint = propagationConstraint.getRemoveConstraint(); } else { String error = String.format("%s.%s in %s is null. %s", Relation.PROPAGATION_CONSTRAINT_PROPERTY, PropagationConstraint.REMOVE_PROPERTY, Utility.toJsonString(element, true), Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); logger.error(error); throw new ResourceRegistryException(error); } } catch(Exception e) { String error = String.format("Error while getting %s from %s while performing RemoveFromContext. %s", Relation.PROPAGATION_CONSTRAINT_PROPERTY, Utility.toJsonString(element, true), Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); logger.error(error); throw new ResourceRegistryException(error, e); } /* * In any removeConstraint value the relation MUST be removed from context to * avoid to have edge having a source outside of the context. */ targetSecurityContext.removeElement(getElement(), oDatabaseDocument); affectedInstances.put(uuid, serializeSelfOnly()); switch(removeConstraint) { case cascade: affectedInstances.putAll(getTargetEntityManagement().internalRemoveFromContext(targetSecurityContext)); break; case cascadeWhenOrphan: OVertex target = (OVertex) getTargetEntityManagement().getElement(); Iterable iterable = target.getEdges(ODirection.IN); Iterator iterator = iterable.iterator(); int count = 0; OEdge edge = null; while(iterator.hasNext()) { edge = (OEdge) iterator.next(); OEdge thisOEdge = (OEdge) getElement(); if(edge.compareTo(thisOEdge) != 0) { if(thisOEdge.getVertex(ODirection.OUT).compareTo(edge.getVertex(ODirection.OUT)) != 0) { count++; break; } /* * else{ ContextUtility.removeFromActualContext(orientGraph, edge); } */ } } if(count > 0) { logger.trace( "{} point to {} which is not orphan ({} exists). Giving {} directive, it will be not remove from {}.", element, target, edge, removeConstraint, targetSecurityContext); } else { affectedInstances.putAll(getTargetEntityManagement().internalRemoveFromContext(targetSecurityContext)); } break; case keep: break; default: break; } return affectedInstances; } @Override protected boolean reallyDelete() throws RelationNotFoundException, ResourceRegistryException { logger.debug("Going to remove {} with UUID {}. Related {}s will be detached.", accessType.getName(), uuid, targetEntityClass.getSimpleName()); getElement(); RemoveConstraint removeConstraint = RemoveConstraint.keep; try { PropagationConstraint propagationConstraint = Utility.getPropertyDocument(PropagationConstraint.class, element, Relation.PROPAGATION_CONSTRAINT_PROPERTY); if(propagationConstraint.getRemoveConstraint() != null) { removeConstraint = propagationConstraint.getRemoveConstraint(); } else { String error = String.format("%s.%s in %s is null. %s", Relation.PROPAGATION_CONSTRAINT_PROPERTY, PropagationConstraint.REMOVE_PROPERTY, Utility.toJsonString(element, true), Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); logger.error(error); throw new ResourceRegistryException(error); } } catch(Exception e) { logger.warn("Error while getting {} from {}. Assuming {}. {}", Relation.PROPAGATION_CONSTRAINT_PROPERTY, Utility.toJsonString(element, true), removeConstraint, Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); } OVertex target = getTargetEntityManagement().getElement(); element.delete(); switch(removeConstraint) { case cascade: getTargetEntityManagement().internalDelete(); break; case cascadeWhenOrphan: Iterable iterable = target.getEdges(ODirection.IN); Iterator iterator = iterable.iterator(); if(iterator.hasNext()) { logger.trace("{} point to {} which is not orphan. Giving {} directive, it will be keep.", element, target, removeConstraint); } else { getTargetEntityManagement().internalDelete(); } break; case keep: break; default: break; } return true; } @SuppressWarnings("unchecked") protected Collection serializeEdges(Iterable edges, boolean postFilterPolymorphic) throws ResourceRegistryException { Map visitedSourceResources = new HashMap<>(); for(ODocument d : edges) { OEdge edge = (OEdge) d; // TODO check because it was using compare if(postFilterPolymorphic && getOClass().isSubClassOf(elementType)) { continue; } RelationManagement relationManagement = ElementManagementUtility.getRelationManagement(getWorkingContext(), oDatabaseDocument, edge); visitedSourceResources = relationManagement.fullSerialize(visitedSourceResources); } return visitedSourceResources.values(); } protected String serializeJsonNodeCollectionAsString(Collection collection) throws ResourceRegistryException { try { ObjectMapper objectMapper = new ObjectMapper(); ArrayNode arrayNode = objectMapper.valueToTree(collection); return objectMapper.writeValueAsString(arrayNode); } catch(Exception e) { throw new ResourceRegistryException(e); } } @Override public String reallyGetAll(boolean polymorphic) throws ResourceRegistryException { Iterable edges = oDatabaseDocument.browseClass(elementType, polymorphic); Collection collection = serializeEdges(edges, false); return serializeJsonNodeCollectionAsString(collection); } @Override public Map addToContext(UUID contextUUID) throws NotFoundException, ContextException { logger.debug("Going to add {} with UUID {} to Context with UUID {}", accessType.getName(), uuid, contextUUID); try { oDatabaseDocument = ContextUtility.getAdminSecurityContext().getDatabaseDocument(PermissionMode.WRITER); SecurityContext targetSecurityContext = ContextUtility.getInstance().getSecurityContextByUUID(contextUUID); Map added = forcedAddToContext(targetSecurityContext); oDatabaseDocument.commit(); logger.info("{} with UUID {} successfully added to Context with UUID {}", accessType.getName(), uuid, contextUUID); return added; } catch(Exception e) { logger.error("Unable to add {} with UUID {} to Context with UUID {}", accessType.getName(), uuid, contextUUID, e); if(oDatabaseDocument != null) { oDatabaseDocument.rollback(); } throw new ContextException(e); } finally { if(oDatabaseDocument != null) { oDatabaseDocument.close(); } } } }