package org.gcube.informationsystem.resourceregistry.contexts.entities; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; 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.NullNode; import org.gcube.com.fasterxml.jackson.databind.node.ObjectNode; import org.gcube.informationsystem.base.reference.AccessType; import org.gcube.informationsystem.base.reference.IdentifiableElement; import org.gcube.informationsystem.contexts.reference.entities.Context; import org.gcube.informationsystem.contexts.reference.relations.IsParentOf; 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.ContextAlreadyPresentException; import org.gcube.informationsystem.resourceregistry.api.exceptions.contexts.ContextException; import org.gcube.informationsystem.resourceregistry.api.exceptions.contexts.ContextNotFoundException; 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.relations.IsParentOfManagement; import org.gcube.informationsystem.resourceregistry.contexts.security.ContextSecurityContext; import org.gcube.informationsystem.resourceregistry.contexts.security.SecurityContext; import org.gcube.informationsystem.resourceregistry.instances.base.entities.EntityElementManagement; import org.gcube.informationsystem.resourceregistry.queries.operators.ComparisonOperator; import org.gcube.informationsystem.resourceregistry.queries.operators.LogicalOperator; import org.gcube.informationsystem.resourceregistry.requests.RequestUtility; import org.gcube.informationsystem.resourceregistry.requests.ServerRequestInfo; import org.gcube.informationsystem.resourceregistry.utils.OrientDBUtility; import org.gcube.informationsystem.serialization.ElementMapper; import org.gcube.informationsystem.types.reference.entities.EntityType; import org.gcube.informationsystem.utils.UUIDManager; import org.gcube.informationsystem.utils.UUIDUtility; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.orientechnologies.orient.core.db.document.ODatabaseDocument; 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; import com.orientechnologies.orient.core.sql.executor.OResultSet; /** * @author Luca Frosini (ISTI - CNR) */ public class ContextManagement extends EntityElementManagement { private static Logger logger = LoggerFactory.getLogger(ContextManagement.class); protected String name; protected Integer forceOffset; protected Integer forceLimit; public void setForceOffset(Integer forceOffset) { this.forceOffset = forceOffset; } public void setForceLimit(Integer forceLimit) { this.forceLimit = forceLimit; } private void init() { this.ignoreStartWithKeys.add(Context.PARENT_PROPERTY); this.ignoreStartWithKeys.add(Context.CHILDREN_PROPERTY); this.typeName = Context.NAME; this.forceIncludeMeta = true; this.forceIncludeAllMeta = true; this.forceOffset = null; this.forceLimit = null; } public ContextManagement() { super(AccessType.CONTEXT); init(); } public ContextManagement(ODatabaseDocument oDatabaseDocument) throws ResourceRegistryException { this(); this.oDatabaseDocument = oDatabaseDocument; getWorkingContext(); } @Override public Map getAffectedInstances() { throw new UnsupportedOperationException(); } public String getName() { if (name == null) { if (element == null) { if (jsonNode != null) { name = jsonNode.get(Context.NAME_PROPERTY).asText(); } } else { name = element.getProperty(Context.NAME_PROPERTY); } } return name; } @Override protected SecurityContext getWorkingContext() throws ResourceRegistryException { if (workingContext == null) { workingContext = ContextSecurityContext.getInstance(); } return workingContext; } @Override protected ContextNotFoundException getSpecificNotFoundException(NotFoundException e) { return new ContextNotFoundException(e.getMessage(), e.getCause()); } @Override protected ContextAlreadyPresentException getSpecificAlreadyPresentException(String message) { return new ContextAlreadyPresentException(message); } protected void checkContext(ContextManagement parentContext) throws ContextNotFoundException, ContextAlreadyPresentException, ResourceRegistryException { StringBuffer select = new StringBuffer(); StringBuffer errorMessage = new StringBuffer(); if (parentContext != null) { String parentId = parentContext.getElement().getIdentity().toString(); select.append("SELECT FROM (TRAVERSE out("); select.append(IsParentOf.NAME); select.append(") FROM "); select.append(parentId); select.append(" MAXDEPTH 1) WHERE "); select.append(Context.NAME_PROPERTY); select.append(ComparisonOperator.EQ.getDbOperator()); select.append("\""); select.append(getName()); select.append("\""); select.append(LogicalOperator.AND.getDbOperator()); select.append(IdentifiableElement.ID_PROPERTY); select.append(ComparisonOperator.NE.getDbOperator()); select.append("\""); select.append(parentContext.uuid); select.append("\""); errorMessage.append("A "); errorMessage.append(Context.NAME); errorMessage.append(" with "); errorMessage.append(this.getName()); errorMessage.append(" has been already created as child of "); errorMessage.append(parentContext.getElement().toString()); } else { select.append("SELECT FROM "); select.append(Context.NAME); select.append(" WHERE "); select.append(Context.NAME_PROPERTY); select.append(ComparisonOperator.EQ.getDbOperator()); select.append("\""); select.append(getName()); select.append("\""); select.append(LogicalOperator.AND.getDbOperator()); select.append("in(\""); select.append(IsParentOf.NAME); select.append("\").size() = 0"); errorMessage.append("A root "); errorMessage.append(Context.NAME); errorMessage.append(" with "); errorMessage.append(this.getName()); errorMessage.append(" already exist"); } logger.trace("Checking if {} -> {}", errorMessage, select); OResultSet resultSet = oDatabaseDocument.command(select.toString(), new HashMap<>()); if (resultSet != null && resultSet.hasNext()) { throw new ContextAlreadyPresentException(errorMessage.toString()); } } private JsonNode filterFieldsByRole(JsonNode context) { // TODO return extra info only to authorized users return context; } @Override protected JsonNode createCompleteJsonNode() throws ResourceRegistryException { JsonNode context = serializeSelfAsJsonNode(); context = filterFieldsByRole(context); int count = 0; Iterable parents = getElement().getEdges(ODirection.IN); for (OEdge edge : parents) { if (++count > 1) { throw new ContextException("A " + Context.NAME + " can not have more than one parent"); } try { IsParentOfManagement isParentOfManagement = new IsParentOfManagement(oDatabaseDocument); isParentOfManagement.setElement(edge); isParentOfManagement.includeSource(true); isParentOfManagement.includeTarget(false); JsonNode isParentOf = isParentOfManagement.createCompleteJsonNode(); if (isParentOf != null) { ((ObjectNode) context).replace(Context.PARENT_PROPERTY, isParentOf); } } catch (Exception e) { logger.error("Unable to correctly serialize {}. {}", edge, OrientDBUtility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); throw new ContextException(""); } } Iterable childrenEdges = getElement().getEdges(ODirection.OUT); for (OEdge edge : childrenEdges) { IsParentOfManagement isParentOfManagement = new IsParentOfManagement(oDatabaseDocument); isParentOfManagement.setElement(edge); try { JsonNode isParentOf = isParentOfManagement.serializeAsJsonNode(); context = addRelation(context, isParentOf, Context.CHILDREN_PROPERTY); } catch (ResourceRegistryException e) { logger.error("Unable to correctly serialize {}. {}", edge, OrientDBUtility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); throw e; } catch (Exception e) { logger.error("Unable to correctly serialize {}. {}", edge, OrientDBUtility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); throw new ResourceRegistryException(e); } } return context; } @Override protected OVertex reallyCreate() throws AlreadyPresentException, ResourceRegistryException { SecurityContext securityContext = null; SecurityContext parentSecurityContext = null; try { JsonNode isParentOfJsonNode = jsonNode.get(Context.PARENT_PROPERTY); if (isParentOfJsonNode != null && !(isParentOfJsonNode instanceof NullNode)) { JsonNode parentJsonNode = isParentOfJsonNode.get(Relation.SOURCE_PROPERTY); ContextManagement parentContextManagement = new ContextManagement(oDatabaseDocument); parentContextManagement.setJsonNode(parentJsonNode); UUID parentUUID = parentContextManagement.uuid; parentSecurityContext = ContextUtility.getInstance().getSecurityContextByUUID(parentUUID); checkContext(parentContextManagement); if (uuid == null) { uuid = UUIDManager.getInstance().generateValidUUID(); } logFullPath(); createVertex(); IsParentOfManagement isParentOfManagement = new IsParentOfManagement(oDatabaseDocument); isParentOfManagement.setJsonNode(isParentOfJsonNode); isParentOfManagement.setSourceEntityManagement(parentContextManagement); isParentOfManagement.setTargetEntityManagement(this); isParentOfManagement.internalCreate(); } else { checkContext(null); logFullPath(); createVertex(); } securityContext = new SecurityContext(uuid); securityContext.setParentSecurityContext(parentSecurityContext); securityContext.create(oDatabaseDocument); ContextUtility.getInstance().addSecurityContext(securityContext); return getElement(); } catch (Exception e) { oDatabaseDocument.rollback(); if (securityContext != null) { securityContext.delete(oDatabaseDocument); if (parentSecurityContext != null && securityContext != null) { parentSecurityContext.getChildren().remove(securityContext); } ServerContextCache.getInstance().cleanCache(); } throw e; } } /** * TODO * The full path of the context is added for every non safe action * - At creation time * - At rename time (see #25139) * - At parent change time (see #26544) * * In this way we are sure to track context fullpath changes. * Furthermore when a context is delete and is mode to the cemetery * i.e. ShadowContextSecurityContext (see #19428) we do not have to do nothing with * the fullpath just move the vertex in the new SecurityContext * and add the instance of context deletion */ protected void logFullPath() { } @Override protected OVertex reallyUpdate() throws NotFoundException, ResourceRegistryException { boolean parentChanged = false; boolean nameChanged = false; OVertex parent = null; boolean found = false; Iterable iterable = getElement().getVertices(ODirection.IN, IsParentOf.NAME); for (OVertex p : iterable) { if (found) { String message = String.format("{} has more than one parent. {}", Context.NAME, OrientDBUtility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); throw new ResourceRegistryException(message.toString()); } parent = p; found = true; } ContextManagement actualParentContextManagement = null; if (parent != null) { actualParentContextManagement = new ContextManagement(oDatabaseDocument); actualParentContextManagement.setElement(parent); } ContextManagement newParentContextManagement = actualParentContextManagement; JsonNode isParentOfJsonNode = jsonNode.get(Context.PARENT_PROPERTY); JsonNode parentContextJsonNode = null; if (isParentOfJsonNode != null && !(isParentOfJsonNode instanceof NullNode)) { parentContextJsonNode = isParentOfJsonNode.get(Relation.SOURCE_PROPERTY); } if (parentContextJsonNode != null && !(parentContextJsonNode instanceof NullNode)) { UUID parentUUID = UUIDUtility.getUUID(parentContextJsonNode); if (actualParentContextManagement != null) { if (parentUUID.compareTo(actualParentContextManagement.uuid) != 0) { parentChanged = true; } } else { parentChanged = true; } if (parentChanged) { newParentContextManagement = new ContextManagement(oDatabaseDocument); newParentContextManagement.setJsonNode(parentContextJsonNode); } } else { if (actualParentContextManagement != null) { parentChanged = true; newParentContextManagement = null; } } String oldName = getElement().getProperty(Context.NAME_PROPERTY); String newName = jsonNode.get(Context.NAME_PROPERTY).asText(); if (oldName.compareTo(newName) != 0) { nameChanged = true; name = newName; } if (parentChanged || nameChanged) { checkContext(newParentContextManagement); } if (parentChanged) { move(newParentContextManagement, false); } logFullPath(); element = (OVertex) updateProperties(oClass, getElement(), jsonNode, ignoreKeys, ignoreStartWithKeys); ServerContextCache.getInstance().cleanCache(); return element; } private void move(ContextManagement newParentContextManagement, boolean check) throws ContextNotFoundException, ContextAlreadyPresentException, ResourceRegistryException { if (check) { checkContext(newParentContextManagement); } SecurityContext newParentSecurityContext = null; // Removing the old parent relationship if any Iterable edges = getElement().getEdges(ODirection.IN, IsParentOf.NAME); if (edges != null && edges.iterator().hasNext()) { Iterator edgeIterator = edges.iterator(); OEdge edge = edgeIterator.next(); IsParentOfManagement isParentOfManagement = new IsParentOfManagement(); isParentOfManagement.setElement(edge); isParentOfManagement.internalDelete(); if (edgeIterator.hasNext()) { throw new ContextException( "Seems that the Context has more than one Parent. " + OrientDBUtility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); } } if (newParentContextManagement != null) { JsonNode isParentOfJsonNode = jsonNode.get(Context.PARENT_PROPERTY); IsParentOfManagement isParentOfManagement = new IsParentOfManagement(oDatabaseDocument); isParentOfManagement.setJsonNode(isParentOfJsonNode); isParentOfManagement.setSourceEntityManagement(newParentContextManagement); isParentOfManagement.setTargetEntityManagement(this); isParentOfManagement.internalCreate(); newParentSecurityContext = ContextUtility.getInstance() .getSecurityContextByUUID(newParentContextManagement.uuid); } SecurityContext thisSecurityContext = ContextUtility.getInstance().getSecurityContextByUUID(uuid); thisSecurityContext.changeParentSecurityContext(newParentSecurityContext, oDatabaseDocument); } @Override protected void reallyDelete() throws NotFoundException, ResourceRegistryException { Iterable iterable = getElement().getEdges(ODirection.OUT); Iterator iterator = iterable.iterator(); while (iterator.hasNext()) { throw new ContextException("Cannot remove a " + Context.NAME + " having children"); } // TODO Move the vertex to the ShadowContextSecurityContext (i.e the cemetery) element.delete(); ContextUtility contextUtility = ContextUtility.getInstance(); SecurityContext securityContext = contextUtility.getSecurityContextByUUID(uuid); securityContext.delete(oDatabaseDocument); ServerContextCache.getInstance().cleanCache(); } @Override public String reallyGetAll(boolean polymorphic) throws ResourceRegistryException { ObjectMapper objectMapper = new ObjectMapper(); ArrayNode arrayNode = objectMapper.createArrayNode(); ServerRequestInfo requestInfo = RequestUtility.getRequestInfo().get(); Integer limit = requestInfo.getLimit(); if(forceLimit!=null) { limit = forceLimit; } if(limit == null) { limit = -1; } Integer offset = requestInfo.getOffset(); if(forceOffset!=null) { offset = forceOffset; } if(offset == null) { offset = 0; } int position = -1; int count = 0; Iterable iterable = oDatabaseDocument.browseClass(typeName, polymorphic); for (ODocument vertex : iterable) { if(++position < offset) { continue; } ContextManagement contextManagement = new ContextManagement(); contextManagement.setForceIncludeMeta(forceIncludeMeta); contextManagement.setForceIncludeAllMeta(forceIncludeAllMeta); contextManagement.setElement((OVertex) vertex); try { JsonNode jsonObject = contextManagement.serializeAsJsonNode(); arrayNode.add(jsonObject); if(limit > 0 && ++count >= limit) { break; } } catch (ResourceRegistryException e) { logger.error("Unable to correctly serialize {}. It will be excluded from results. {}", vertex.toString(), OrientDBUtility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); } } try { return objectMapper.writeValueAsString(arrayNode); } catch (JsonProcessingException e) { throw new ResourceRegistryException(e); } } public String allFromDatabase(boolean polymorphic) throws ResourceRegistryException { return super.all(polymorphic); } @Override public String all(boolean polymorphic) throws ResourceRegistryException { try { ServerContextCache contextCache = ServerContextCache.getInstance(); List contexts = contextCache.getContexts(); ServerRequestInfo requestInfo = RequestUtility.getRequestInfo().get(); Integer limit = requestInfo.getLimit(); if(forceLimit!=null) { limit = forceLimit; }else if(limit == null) { limit = -1; } Integer offset = requestInfo.getOffset(); if(forceOffset!=null) { offset = forceOffset; }else if(offset == null) { offset = 0; } int position = -1; int count = 0; if(offset==0 && limit<=0) { return ElementMapper.marshal(contexts); } List requestedContexts = new ArrayList<>(); for (Context c : contexts) { if(++position < offset) { continue; } requestedContexts.add(c); if(limit > 0 && ++count >= limit) { break; } } return ElementMapper.marshal(requestedContexts); } catch (JsonProcessingException | ResourceRegistryException e) { return allFromDatabase(polymorphic); } } public String readFromServer() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException { return super.read().toString(); } public String readAsString() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException { try { ServerContextCache contextCache = ServerContextCache.getInstance(); return ElementMapper.marshal(contextCache.getContextByUUID(uuid)); } catch (JsonProcessingException | ResourceRegistryException e) { return readFromServer(); } } @Override public void sanityCheck() throws SchemaViolationException, ResourceRegistryException { // Nothing to do } }