package org.gcube.informationsystem.resourceregistry.instances.model.entities; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import org.gcube.informationsystem.base.reference.AccessType; import org.gcube.informationsystem.base.reference.entities.BaseEntity; import org.gcube.informationsystem.base.reference.properties.Header; import org.gcube.informationsystem.model.reference.entities.Entity; 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.Relation; 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.EntityAlreadyPresentException; import org.gcube.informationsystem.resourceregistry.api.exceptions.query.InvalidQueryException; import org.gcube.informationsystem.resourceregistry.instances.base.ERManagement; import org.gcube.informationsystem.resourceregistry.instances.base.ERManagementUtility; import org.gcube.informationsystem.resourceregistry.instances.base.entities.BaseEntityManagement; import org.gcube.informationsystem.resourceregistry.instances.model.relations.RelationManagement; import org.gcube.informationsystem.resourceregistry.security.SecurityContext; import org.gcube.informationsystem.resourceregistry.security.SecurityContext.PermissionMode; import org.gcube.informationsystem.resourceregistry.utils.Utility; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; 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.OElement; import com.orientechnologies.orient.core.record.OVertex; import com.orientechnologies.orient.core.record.impl.ODocument; import com.orientechnologies.orient.core.sql.executor.OResult; import com.orientechnologies.orient.core.sql.executor.OResultSet; /** * @author Luca Frosini (ISTI - CNR) */ public abstract class EntityManagement extends BaseEntityManagement { /** * Provide a cache edge-internal-id -> RelationManagement * this avoid to recreate the relationManagement of already visited edges */ @SuppressWarnings("rawtypes") protected Map relationManagements; protected EntityManagement(AccessType accessType) { super(accessType); this.ignoreKeys.add(Entity.HEADER_PROPERTY); this.ignoreStartWithKeys.add(com.tinkerpop.blueprints.impls.orient.OrientVertex.CONNECTION_IN_PREFIX.toLowerCase()); this.ignoreStartWithKeys.add(com.tinkerpop.blueprints.impls.orient.OrientVertex.CONNECTION_OUT_PREFIX.toLowerCase()); this.ignoreStartWithKeys.add(com.tinkerpop.blueprints.impls.orient.OrientVertex.CONNECTION_IN_PREFIX.toUpperCase()); this.ignoreStartWithKeys.add(com.tinkerpop.blueprints.impls.orient.OrientVertex.CONNECTION_OUT_PREFIX.toUpperCase()); this.relationManagements = new HashMap<>(); } protected EntityManagement(AccessType accessType, SecurityContext workingContext, ODatabaseDocument orientGraph) { this(accessType); this.oDatabaseDocument = orientGraph; setWorkingContext(workingContext); } @SuppressWarnings("rawtypes") /* * It works perfectly in case of any kind of update. In case of use from create * the cache does not work by using the ID because until commit the edge has a * fake id starting with - (minus) sign. This not imply any collateral effect * but a better solution is a desiderata. */ protected RelationManagement getRelationManagement(OEdge edge) throws ResourceRegistryException { String id = edge.getIdentity().toString(); RelationManagement relationManagement = relationManagements.get(id); if(relationManagement == null) { relationManagement = ERManagementUtility.getRelationManagement(getWorkingContext(), oDatabaseDocument, edge); relationManagements.put(id, relationManagement); } return relationManagement; } protected void addToRelationManagement(@SuppressWarnings("rawtypes") RelationManagement relationManagement) throws ResourceRegistryException { OElement elem = relationManagement.getElement(); String id = elem.getIdentity().toString(); if(relationManagements.get(id) != null && relationManagements.get(id) != relationManagement) { StringBuilder errorMessage = new StringBuilder(); errorMessage.append("Two different instance of "); errorMessage.append(relationManagement.getClass().getSimpleName()); errorMessage.append(" point to the same "); errorMessage.append(elem.getClass().getSimpleName()); errorMessage.append(". "); errorMessage.append(Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); throw new ResourceRegistryException(errorMessage.toString()); } relationManagements.put(id, relationManagement); } protected static JsonNode addRelation(JsonNode sourceResource, JsonNode relation, String arrayKey) throws ResourceRegistryException { ObjectMapper objectMapper = new ObjectMapper(); ArrayNode relationArray = objectMapper.createArrayNode(); try { if(sourceResource.has(arrayKey)) { relationArray = (ArrayNode) sourceResource.get(arrayKey); } relationArray.add(relation); ((ObjectNode) sourceResource).replace(arrayKey, relationArray); } catch(Exception e) { throw new ResourceRegistryException(e); } return sourceResource; } protected OVertex createVertex() throws EntityAlreadyPresentException, ResourceRegistryException { logger.trace("Going to create {} for {} ({}) using {}", OVertex.class.getSimpleName(), accessType.getName(), elementType, jsonNode); try { if(oClass.isAbstract()) { String error = String.format( "Trying to create an instance of %s of type %s which is abstract. The operation will be aborted.", accessType.getName(), elementType); throw new ResourceRegistryException(error); } OVertex vertexEntity = oDatabaseDocument.newVertex(elementType); try { if(uuid != null) { OVertex v = getElement(); if(v != null) { String error = String.format("A %s with UUID %s already exist", elementType, uuid.toString()); throw getSpecificERAlreadyPresentException(error); } } } catch(NotFoundException e) { try { OElement el = ERManagementUtility.getAnyElementByUUID(uuid); String error = String.format("UUID %s is already used by another %s. This is not allowed.", uuid.toString(), (el instanceof OVertex) ? Entity.NAME : Relation.NAME); throw getSpecificERAvailableInAnotherContextException(error); } catch(NotFoundException e1) { // OK the UUID is not already used. } } catch(AvailableInAnotherContextException e) { throw e; } this.element = vertexEntity; if(accessType == AccessType.RESOURCE) { // Facet and relation are created in calling method } else { ERManagement.updateProperties(oClass, element, jsonNode, ignoreKeys, ignoreStartWithKeys); } logger.info("Created {} is {}", OVertex.class.getSimpleName(), Utility.toJsonString((OVertex) element, true)); return element; } catch(ResourceRegistryException e) { throw e; } catch(Exception e) { logger.trace("Error while creating {} for {} ({}) using {}", OVertex.class.getSimpleName(), accessType.getName(), elementType, jsonNode, e); throw new ResourceRegistryException("Error Creating " + elementType + " with " + jsonNode, e.getCause()); } } @Override protected boolean reallyAddToContext(SecurityContext targetSecurityContext) throws ContextException, ResourceRegistryException { targetSecurityContext.addElement(getElement(), oDatabaseDocument); Iterable edges = getElement().getEdges(ODirection.OUT); for(OEdge edge : edges) { @SuppressWarnings("rawtypes") RelationManagement relationManagement = getRelationManagement(edge); relationManagement.internalAddToContext(targetSecurityContext); } return true; } @Override protected boolean reallyRemoveFromContext(SecurityContext targetSecurityContext) throws ContextException, ResourceRegistryException { Iterable edges = getElement().getEdges(ODirection.OUT); for(OEdge edge : edges) { @SuppressWarnings("rawtypes") RelationManagement relationManagement = getRelationManagement(edge); relationManagement.internalRemoveFromContext(targetSecurityContext); } targetSecurityContext.removeElement(getElement(), oDatabaseDocument); return true; } @Override public String reallyGetAll(boolean polymorphic) throws ResourceRegistryException { ObjectMapper objectMapper = new ObjectMapper(); ArrayNode arrayNode = objectMapper.createArrayNode(); Iterable iterable = oDatabaseDocument.browseClass(elementType, polymorphic); for(ODocument vertex : iterable) { EntityManagement entityManagement = ERManagementUtility.getEntityManagement(getWorkingContext(), oDatabaseDocument, (OVertex) vertex); try { JsonNode jsonNode = entityManagement.serializeAsJson(); arrayNode.add(jsonNode); } catch(ResourceRegistryException e) { logger.error("Unable to correctly serialize {}. It will be excluded from results. {}", vertex.toString(), Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); } } try { return objectMapper.writeValueAsString(arrayNode); } catch(JsonProcessingException e) { throw new ResourceRegistryException(e); } } public String reallyQuery(String relationType, String referenceType, UUID referenceUUID, ODirection direction, boolean polymorphic, Map constraint) throws ResourceRegistryException { ObjectMapper objectMapper = new ObjectMapper(); ArrayNode arrayNode = objectMapper.createArrayNode(); Iterable references = null; if(referenceUUID != null) { OElement element = ERManagementUtility.getAnyElementByUUID(referenceUUID); if(element instanceof OVertex) { @SuppressWarnings("unchecked") EntityManagement entityManagement = ERManagementUtility.getEntityManagement(getWorkingContext(), oDatabaseDocument, (OVertex) element); String elementType = entityManagement.getElementType(); if(elementType.compareTo(referenceType) != 0) { if(polymorphic && getOClass().isSubClassOf(referenceType)) { // OK } else { String error = String.format("Referenced instace with UUID %s is not a %s", referenceUUID, referenceType); throw new InvalidQueryException(error); } } List vertexes = new ArrayList<>(); vertexes.add((OVertex) element); references = vertexes; } else { String error = String.format("Referenced instace with UUID %s is not an %s", referenceUUID, Entity.NAME); throw new InvalidQueryException(error); } } else { references = oDatabaseDocument.browseClass(referenceType, polymorphic); } for(Object r : references) { OVertex v = (OVertex) r; List directions = new ArrayList<>(); if(direction==ODirection.BOTH) { directions.add(ODirection.IN); directions.add(ODirection.OUT); }else { directions.add(direction); } for(ODirection d : directions) { Iterable edges = v.getEdges(d.opposite(), relationType); for(OEdge edge : edges) { OVertex vertex = edge.getVertex(d); if(v.getIdentity().compareTo(vertex.getIdentity()) == 0) { continue; } OClass oClass = ERManagement.getOClass(vertex); if(polymorphic && oClass.isSubClassOf(elementType)) { // OK } else { // excluding from results continue; } @SuppressWarnings("rawtypes") EntityManagement entityManagement = ERManagementUtility.getEntityManagement(getWorkingContext(), oDatabaseDocument, vertex); try { if(referenceUUID!=null && entityManagement.getUUID().compareTo(referenceUUID) == 0) { continue; } JsonNode jsonNode = entityManagement.serializeAsJson(); arrayNode.add(jsonNode); } catch(ResourceRegistryException e) { logger.error("Unable to correctly serialize {}. It will be excluded from results. {}", vertex.toString(), Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); } } } } try { return objectMapper.writeValueAsString(arrayNode); } catch(JsonProcessingException e) { throw new ResourceRegistryException(e); } } public String reallyQueryTraversal(String relationType, String referenceType, UUID referenceUUID, ODirection direction, boolean polymorphic, Map constraint) throws ResourceRegistryException { ObjectMapper objectMapper = new ObjectMapper(); ArrayNode arrayNode = objectMapper.createArrayNode(); if(referenceUUID != null) { constraint.put(Entity.HEADER_PROPERTY + "." + Header.UUID_PROPERTY, referenceUUID.toString()); } // TODO check types /* * SELECT FROM (TRAVERSE inE('isIdentifiedBy'), outV('EService') FROM (SELECT * FROM SoftwareFacet WHERE group='VREManagement' AND name='SmartExecutor')) * * WHERE @class='EService' // Only is not polymorphic */ StringBuilder selectStringBuilder = new StringBuilder("SELECT FROM (TRAVERSE "); selectStringBuilder.append(direction.name().toLowerCase()); selectStringBuilder.append("E('"); selectStringBuilder.append(relationType); selectStringBuilder.append("'), "); selectStringBuilder.append(direction.opposite().name().toLowerCase()); selectStringBuilder.append("V('"); selectStringBuilder.append(elementType); selectStringBuilder.append("') FROM (SELECT FROM "); selectStringBuilder.append(referenceType); boolean first = true; for(String key : constraint.keySet()) { if(first) { selectStringBuilder.append(" WHERE "); first = false; } else { selectStringBuilder.append(" AND "); } selectStringBuilder.append(key); selectStringBuilder.append("="); String value = constraint.get(key).trim(); selectStringBuilder.append("'"); selectStringBuilder.append(value); selectStringBuilder.append("'"); } selectStringBuilder.append(" ))"); if(!polymorphic) { selectStringBuilder.append(" WHERE @class='"); selectStringBuilder.append(elementType); selectStringBuilder.append("'"); } String select = selectStringBuilder.toString(); logger.trace(select); OResultSet resultSet = oDatabaseDocument.command(select,new HashMap<>()); while(resultSet.hasNext()) { OResult oResult = resultSet.next(); OElement element = ERManagement.getElementFromOptional(oResult.getElement()); if(polymorphic) { OClass oClass = null; try { if(element instanceof OEdge) { continue; } oClass = ERManagement.getOClass(element); } catch(Exception e) { String error = String.format("Unable to detect type of %s. %s", element.toString(), Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); logger.error(error, e); throw new ResourceRegistryException(error); } if(oClass.isSubClassOf(elementType)) { continue; } } OVertex vertex = (OVertex) element; @SuppressWarnings("rawtypes") EntityManagement entityManagement = ERManagementUtility.getEntityManagement(getWorkingContext(), oDatabaseDocument, vertex); try { if(constraint.containsKey(Entity.HEADER_PROPERTY + "." + Header.UUID_PROPERTY)) { String uuid = constraint.get(Entity.HEADER_PROPERTY + "." + Header.UUID_PROPERTY); if(entityManagement.getUUID().compareTo(UUID.fromString(uuid)) == 0) { continue; } } JsonNode jsonNode = entityManagement.serializeAsJson(); arrayNode.add(jsonNode); } catch(ResourceRegistryException e) { logger.error("Unable to correctly serialize {}. It will be excluded from results. {}", vertex.toString(), Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE); } } try { return objectMapper.writeValueAsString(arrayNode); } catch(JsonProcessingException e) { throw new ResourceRegistryException(e); } } public String query(String relationType, String referenceType, UUID referenceUUID, ODirection direction, boolean polymorphic, Map constraint) throws ResourceRegistryException { try { oDatabaseDocument = getWorkingContext().getDatabaseDocument(PermissionMode.READER); AccessType relationAccessType = ERManagementUtility.getBaseAccessType(relationType); if(relationAccessType != AccessType.IS_RELATED_TO && relationAccessType != AccessType.CONSISTS_OF) { String error = String.format("%s must be a relation type", relationType); throw new ResourceRegistryException(error); } AccessType referenceAccessType = ERManagementUtility.getBaseAccessType(referenceType); if(referenceAccessType != AccessType.RESOURCE && referenceAccessType != AccessType.FACET) { String error = String.format("%s must be a en entity type", referenceType); throw new ResourceRegistryException(error); } if(constraint == null) { constraint = new HashMap<>(); } switch(accessType) { case RESOURCE: if(relationAccessType == AccessType.CONSISTS_OF) { if(direction != ODirection.OUT) { String error = String.format("%s can only goes %s from %s.", relationType, ODirection.OUT.name(), elementType); throw new InvalidQueryException(error); } else { if(referenceAccessType != AccessType.FACET) { String error = String.format("%s can only has as target a %s. Provided instead %s : %s", relationType, Facet.NAME, referenceAccessType, referenceType); throw new InvalidQueryException(error); } } } break; case FACET: if(relationAccessType != AccessType.CONSISTS_OF || direction != ODirection.IN || referenceAccessType != AccessType.RESOURCE) { String error = String.format("%s can only has %s %s from a %s.", elementType, ODirection.IN.name(), ConsistsOf.NAME, Resource.NAME); throw new InvalidQueryException(error); } break; default: break; } return reallyQuery(relationType, referenceType, referenceUUID, direction, polymorphic, constraint); } catch(ResourceRegistryException e) { throw e; } catch(Exception e) { throw new ResourceRegistryException(e); } finally { if(oDatabaseDocument != null) { oDatabaseDocument.close(); } } } }