All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ai.stapi.graphoperations.graphLoader.inmemory.InMemoryGraphLoader Maven / Gradle / Ivy

There is a newer version: 0.3.2
Show newest version
package ai.stapi.graphoperations.graphLoader.inmemory;

import ai.stapi.graph.AttributeContainer;
import ai.stapi.graph.Graph;
import ai.stapi.graph.attribute.Attribute;
import ai.stapi.graph.attribute.LeafAttribute;
import ai.stapi.graph.attribute.ListAttribute;
import ai.stapi.graph.attribute.attributeValue.IdAttributeValue;
import ai.stapi.graph.graphelements.Edge;
import ai.stapi.graph.graphelements.Node;
import ai.stapi.graph.inMemoryGraph.InMemoryGraphRepository;
import ai.stapi.graph.traversableGraphElements.TraversableEdge;
import ai.stapi.graph.traversableGraphElements.TraversableGraphElement;
import ai.stapi.graph.traversableGraphElements.TraversableNode;
import ai.stapi.graph.versionedAttributes.VersionedAttribute;
import ai.stapi.graphoperations.graphLanguage.graphDescription.GraphDescription;
import ai.stapi.graphoperations.graphLanguage.graphDescription.specific.positive.AbstractAttributeDescription;
import ai.stapi.graphoperations.graphLanguage.graphDescription.specific.positive.AbstractEdgeDescription;
import ai.stapi.graphoperations.graphLanguage.graphDescription.specific.positive.AbstractNodeDescription;
import ai.stapi.graphoperations.graphLanguage.graphDescription.specific.positive.AllAttributesDescription;
import ai.stapi.graphoperations.graphLanguage.graphDescription.specific.positive.AttributeDescriptionParameters;
import ai.stapi.graphoperations.graphLanguage.graphDescription.specific.positive.EdgeDescriptionParameters;
import ai.stapi.graphoperations.graphLanguage.graphDescription.specific.positive.NodeDescriptionParameters;
import ai.stapi.graphoperations.graphLanguage.graphDescription.specific.positive.PositiveGraphDescription;
import ai.stapi.graphoperations.graphLanguage.graphDescription.specific.positive.UuidIdentityDescription;
import ai.stapi.graphoperations.graphLanguage.graphDescription.specific.query.EdgeQueryDescription;
import ai.stapi.graphoperations.graphLanguage.graphDescription.specific.query.GraphElementQueryDescription;
import ai.stapi.graphoperations.graphLanguage.graphDescription.specific.query.NodeQueryGraphDescription;
import ai.stapi.graphoperations.graphLoader.GraphLoader;
import ai.stapi.graphoperations.graphLoader.GraphLoaderFindAsObjectOutput;
import ai.stapi.graphoperations.graphLoader.GraphLoaderGetAsObjectOutput;
import ai.stapi.graphoperations.graphLoader.GraphLoaderReturnType;
import ai.stapi.graphoperations.graphLoader.exceptions.GraphLoaderException;
import ai.stapi.graphoperations.graphLoader.search.SearchQueryParameters;
import ai.stapi.identity.UniqueIdentifier;
import ai.stapi.schema.structureSchema.exception.FieldsNotFoundException;
import ai.stapi.schema.structureSchemaProvider.StructureSchemaFinder;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;

public class InMemoryGraphLoader implements GraphLoader {

  private final InMemoryGraphRepository inMemoryGraphRepository;
  private final InMemoryGenericSearchOptionResolver searchOptionResolver;
  private final StructureSchemaFinder structureSchemaFinder;
  private final ObjectMapper objectMapper;

  public InMemoryGraphLoader(
      InMemoryGraphRepository inMemoryGraphRepository,
      InMemoryGenericSearchOptionResolver searchOptionResolver,
      StructureSchemaFinder structureSchemaFinder,
      ObjectMapper objectMapper
  ) {
    this.inMemoryGraphRepository = inMemoryGraphRepository;
    this.searchOptionResolver = searchOptionResolver;
    this.structureSchemaFinder = structureSchemaFinder;
    this.objectMapper = objectMapper;
  }

  public InMemoryGraphLoader(
      Graph graph,
      InMemoryGenericSearchOptionResolver searchOptionResolver,
      StructureSchemaFinder structureSchemaFinder,
      ObjectMapper objectMapper
  ) {
    this.inMemoryGraphRepository = new InMemoryGraphRepository(graph);
    this.searchOptionResolver = searchOptionResolver;
    this.structureSchemaFinder = structureSchemaFinder;
    this.objectMapper = objectMapper;
  }

  @Override
  public List findAsTraversable(GraphElementQueryDescription graphDescription) {
    var output = this.find(
        graphDescription,
        Object.class,
        GraphLoaderReturnType.GRAPH
    );
    String elementType;
    if (graphDescription instanceof NodeQueryGraphDescription nodeQueryGraphDescription) {
      elementType = ((NodeDescriptionParameters) nodeQueryGraphDescription.getParameters()).getNodeType();
    } else {
      elementType = ((EdgeDescriptionParameters) graphDescription.getParameters()).getEdgeType(); 
    }
    return output.getGraphLoaderFindAsGraphOutput().getFoundGraphElementIds()
        .stream()
        .map(uuid -> output.getGraphLoaderFindAsGraphOutput().getGraph().loadGraphElement(uuid, elementType))
        .toList();
  }

  @Override
  public TraversableGraphElement getAsTraversable(
      UniqueIdentifier elementId,
      GraphElementQueryDescription graphDescription
  ) {
    var graph = this.get(
        elementId,
        graphDescription,
        Object.class,
        GraphLoaderReturnType.GRAPH
    );
    String elementType;
    if (graphDescription instanceof NodeQueryGraphDescription nodeQueryGraphDescription) {
      elementType = ((NodeDescriptionParameters) nodeQueryGraphDescription.getParameters()).getNodeType();
    } else {
      elementType = ((EdgeDescriptionParameters) graphDescription.getParameters()).getEdgeType();
    }
    return graph.getGraph().loadGraphElement(elementId, elementType);
  }

  @Override
  public  GraphLoaderFindAsObjectOutput find(
      GraphElementQueryDescription graphElementQueryDescription,
      Class objectClass,
      GraphLoaderReturnType... returnTypes
  ) {
    var initialElements = this.getTraversableElements(graphElementQueryDescription);
    var graph = new InMemoryGraphRepository();
    var data = new ArrayList();
    initialElements.stream()
        .map(element -> this.resolveStep(element, graphElementQueryDescription, this.convertReturnTypes(returnTypes)))
        .forEach(
            stepOutput -> {
              graph.merge(stepOutput.getGraph());
              if (!stepOutput.getObject().isEmpty()) {
                data.add(this.objectMapper.convertValue(stepOutput.getObject(), objectClass));
              }
            }
        );

    return new GraphLoaderFindAsObjectOutput<>(
        data,
        initialElements.stream().map(TraversableGraphElement::getId).toList(),
        graph
    );
  }

  private Set convertReturnTypes(GraphLoaderReturnType[] returnTypes) {
    if (returnTypes.length == 0) {
      return Set.of(GraphLoaderReturnType.OBJECT);
    }
    return Arrays.stream(returnTypes).collect(Collectors.toSet());
  }

  @Override
  public  GraphLoaderGetAsObjectOutput get(
      UniqueIdentifier elementId,
      GraphElementQueryDescription graphDescription,
      Class objectClass,
      GraphLoaderReturnType... returnTypes
  ) {
    var element = this.getTraversableGraphElement(elementId, graphDescription);
    var graphLoaderStepOutput = this.resolveStep(
        element,
        graphDescription,
        this.convertReturnTypes(returnTypes)
    );
    return new GraphLoaderGetAsObjectOutput<>(
        this.objectMapper.convertValue(graphLoaderStepOutput.getObject(), objectClass),
        graphLoaderStepOutput.getGraph().traversable()
    );
  }

  public Object getSearchOptionAttributeValue(
      UniqueIdentifier elementId,
      PositiveGraphDescription graphDescription
  ) {
    var element = this.getTraversableGraphElement(elementId, graphDescription);
    return this.resolveSearchOptionStep(
        element,
        graphDescription
    );
  }

  @NotNull
  private TraversableGraphElement getTraversableGraphElement(
      UniqueIdentifier elementId,
      PositiveGraphDescription graphDescription
  ) {
    if (graphDescription instanceof AbstractNodeDescription) {
      var param = (NodeDescriptionParameters) graphDescription.getParameters();
      if (this.inMemoryGraphRepository.nodeExists(elementId, param.getNodeType())) {
        return this.inMemoryGraphRepository.loadNode(elementId, param.getNodeType());
      }
    }
    if (graphDescription instanceof AbstractEdgeDescription) {
      var param = (EdgeDescriptionParameters) graphDescription.getParameters();
      if (this.inMemoryGraphRepository.edgeExists(elementId, param.getEdgeType())) {
        return this.inMemoryGraphRepository.loadEdge(elementId, param.getEdgeType());
      }
    }
    throw GraphLoaderException.becauseThereIsNoGraphElementWithProvidedUuid(
        elementId,
        graphDescription
    );
  }

  private List getTraversableElements(GraphElementQueryDescription graphDescription) {
    var searchQueryParameters = graphDescription.getSearchQueryParameters();
    if (graphDescription instanceof AbstractNodeDescription) {
      var params = (NodeDescriptionParameters) graphDescription.getParameters();
      var traversableGraphElements = inMemoryGraphRepository.loadAllNodes(params.getNodeType())
          .stream()
          .map(TraversableGraphElement.class::cast)
          .toList();

      return this.applySearchQueryParameters(
          traversableGraphElements,
          searchQueryParameters,
          graphDescription
      );
    }

    if (graphDescription instanceof AbstractEdgeDescription) {
      var params = (EdgeDescriptionParameters) graphDescription.getParameters();
      var traversableGraphElements = inMemoryGraphRepository.loadAllEdges(params.getEdgeType())
          .stream()
          .map(TraversableGraphElement.class::cast)
          .toList();

      return this.applySearchQueryParameters(
          traversableGraphElements,
          searchQueryParameters,
          graphDescription
      );
    }
    throw new RuntimeException("There should never be any other graph descriptions");
  }

  private List applySearchQueryParameters(
      List traversableGraphElements,
      SearchQueryParameters searchQueryParameters,
      PositiveGraphDescription positiveGraphDescription
  ) {
    var context = new InMemorySearchResolvingContext(
        traversableGraphElements.stream(),
        this.inMemoryGraphRepository,
        positiveGraphDescription
    );
    for (var filter : searchQueryParameters.getFilterOptions()) {
      context = this.searchOptionResolver.resolve(filter, context);
    }
    for (var sort : searchQueryParameters.getSortOptions()) {
      context = this.searchOptionResolver.resolve(sort, context);
    }
    context.applySort();
    var paginationOption = searchQueryParameters.getPaginationOption();
    if (paginationOption != null) {
      return this.searchOptionResolver.resolve(paginationOption, context).getGraphElements();
    }
    return context.getGraphElements();
  }

  private GraphLoaderStepOutput resolveStep(
      TraversableGraphElement element,
      GraphDescription graphDescription,
      Set returnTypes
  ) {
    if (element instanceof TraversableNode node) {
      return this.resolveTraversableNode(
          node,
          (AbstractNodeDescription) graphDescription,
          returnTypes
      );
    }
    if (element instanceof TraversableEdge edge) {
      return this.resolveTraversableEdge(
          edge,
          (AbstractEdgeDescription) graphDescription,
          returnTypes
      );
    }
    throw new RuntimeException("There should never be any other graph element.");
  }

  private GraphLoaderStepOutput resolveTraversableNode(
      TraversableNode node,
      AbstractNodeDescription graphDescription,
      Set returnTypes
  ) {
    var object = new HashMap();
    var graph = new Graph();
    var attributes = this.resolveAttributes(node.getType(), node, graphDescription);
    if (returnTypes.contains(GraphLoaderReturnType.GRAPH)) {
      var inputNode = new Node(node.getId(), node.getType());
      for (Attribute attribute : attributes) {
        inputNode = inputNode.add(attribute);
      }
      graph = graph.with(inputNode);
    }
    var childGraphDescriptions = graphDescription.getChildGraphDescriptions();
    if (returnTypes.contains(GraphLoaderReturnType.OBJECT)) {
      if (childGraphDescriptions.stream().anyMatch(UuidIdentityDescription.class::isInstance)) {
        object.put("id", node.getId().getId());
      }
      attributes.forEach(attribute -> object.put(attribute.getName(), attribute.getValue()));
    }
    for (var childDescription : childGraphDescriptions) {
      if (childDescription instanceof AbstractEdgeDescription edgeDescription) {
        var parameters = (EdgeDescriptionParameters) edgeDescription.getParameters();
        List edges;
        if (edgeDescription.isIngoing()) {
          edges = node.getIngoingEdges(parameters.getEdgeType());
        } else {
          edges = node.getOutgoingEdges(parameters.getEdgeType());
        }
        SearchQueryParameters searchQueryParameters;
        if (edgeDescription instanceof EdgeQueryDescription edgeQueryDescription) {
          searchQueryParameters = edgeQueryDescription.getSearchQueryParameters();
        } else {
          searchQueryParameters = SearchQueryParameters.from();
        }
        ArrayList graphElements = new ArrayList<>(edges);
        var filteredGraphElements = this.applySearchQueryParameters(
            graphElements,
            searchQueryParameters,
            edgeDescription
        );
        var filteredEdges = filteredGraphElements.stream().map(TraversableEdge.class::cast).toList();
        var fieldSchema = this.structureSchemaFinder.getFieldDefinitionOrFallback(
            node.getType(),
            parameters.getEdgeType()
        );
        if (fieldSchema.isList()) {
          var childObjects = new ArrayList<>();
          for (var edge : filteredEdges) {
            var child = this.resolveTraversableEdge(edge, edgeDescription, returnTypes);
            if (returnTypes.contains(GraphLoaderReturnType.GRAPH)) {
              graph = graph.merge(child.getGraph());
            }
            if (returnTypes.contains(GraphLoaderReturnType.OBJECT)) {
              childObjects.add(child.getObject());
            }
          }
          if (returnTypes.contains(GraphLoaderReturnType.OBJECT)) {
            if (
                edgeDescription instanceof EdgeQueryDescription edgeQueryDescription
                    && !edgeQueryDescription.isCompact()
            ) {
              object.put(
                  String.format("%sConnections", parameters.getEdgeType()),
                  childObjects
              );
            } else {
              object.put(parameters.getEdgeType(), childObjects);
            }
          }
        } else {
          Map childObject = null;
          if (!filteredEdges.isEmpty()) {
            var edge = filteredEdges.get(0);
            var child = this.resolveTraversableEdge(edge, edgeDescription, returnTypes);
            if (returnTypes.contains(GraphLoaderReturnType.GRAPH)) {
              graph = graph.merge(child.getGraph());
            }
            if (returnTypes.contains(GraphLoaderReturnType.OBJECT)) {
              childObject = child.getObject();
            }
          }
          if (returnTypes.contains(GraphLoaderReturnType.OBJECT)) {
            if (
                edgeDescription instanceof EdgeQueryDescription edgeQueryDescription
                    && !edgeQueryDescription.isCompact()
            ) {
              object.put(
                  String.format("%sConnections", parameters.getEdgeType()),
                  childObject
              );
            } else {
              object.put(parameters.getEdgeType(), childObject);
            }
          }
        }
      }
    }

    return new GraphLoaderStepOutput(object, graph);
  }

  private GraphLoaderStepOutput resolveTraversableEdge(
      TraversableEdge edge,
      AbstractEdgeDescription graphDescription,
      Set returnTypes
  ) {
    var childNodeDescriptions = graphDescription.getChildGraphDescriptions().stream()
        .filter(AbstractNodeDescription.class::isInstance)
        .map(AbstractNodeDescription.class::cast)
        .toList();

    var graph = new Graph();
    var attributes = this.resolveAttributes(edge.getType(), edge, graphDescription);
    if (returnTypes.contains(GraphLoaderReturnType.GRAPH)) {
      var nodeFrom = new Node(edge.getNodeFromId(), edge.getNodeFromType());
      var nodeTo = new Node(edge.getNodeToId(), edge.getNodeToType());
      var inputEdge = new Edge(edge.getId(), nodeFrom, edge.getType(), nodeTo);
      for (Attribute attribute : attributes) {
        inputEdge = inputEdge.add(attribute);
      }
      graph = new Graph(nodeFrom, nodeTo, inputEdge);
    }
    Map object = new HashMap<>();
    Map edgesObject = new HashMap<>();
    if (this.isMappingToConnectionObject(graphDescription, returnTypes)) {
      object.put("edges", edgesObject);
      attributes.forEach(
          attribute -> edgesObject.put(attribute.getName(), attribute.getValue())
      );
      if (
          graphDescription.getChildGraphDescriptions().stream().anyMatch(UuidIdentityDescription.class::isInstance)
      ) {
        edgesObject.put("id", edge.getId().getId());
      }
    }
    if (childNodeDescriptions.isEmpty()) {
      return new GraphLoaderStepOutput(object, graph);
    }
    TraversableNode otherNode;
    if (graphDescription.isIngoing()) {
      otherNode = edge.getNodeFrom();
    } else {
      otherNode = edge.getNodeTo();
    }
    var foundNodeDescription = childNodeDescriptions.stream()
        .filter(
            nodeDescription -> ((NodeDescriptionParameters) nodeDescription.getParameters())
                .getNodeType()
                .equals(otherNode.getType())
        ).findFirst();

    if (foundNodeDescription.isPresent()) {
      var graphLoaderStepOutput = this.resolveTraversableNode(
          otherNode,
          foundNodeDescription.get(),
          returnTypes
      );
      if (returnTypes.contains(GraphLoaderReturnType.GRAPH)) {
        graph = graph.merge(graphLoaderStepOutput.getGraph());
      }
      if (returnTypes.contains(GraphLoaderReturnType.OBJECT)) {
        if (
            graphDescription instanceof EdgeQueryDescription edgeQueryDescription
                && !edgeQueryDescription.isCompact()
        ) {
          edgesObject.put("node", graphLoaderStepOutput.getObject());
        } else {
          object = graphLoaderStepOutput.getObject();
        }
      }
      return new GraphLoaderStepOutput(object, graph);
    } else {
      return new GraphLoaderStepOutput(new HashMap<>(), new Graph());
    }
  }

  private List> resolveAttributes(
      String graphElementType,
      AttributeContainer graphElement,
      GraphDescription graphDescription
  ) {
    var childGraphDescriptions = graphDescription.getChildGraphDescriptions();
    if (childGraphDescriptions.stream().anyMatch(AllAttributesDescription.class::isInstance)) {
      return graphElement.getVersionedAttributes()
          .getVersionedAttributeList()
          .stream()
          .map(VersionedAttribute::getCurrent)
          .collect(Collectors.toList());
    }
    List> list = new ArrayList<>();
    childGraphDescriptions.stream()
        .filter(AbstractAttributeDescription.class::isInstance)
        .map(description -> (AttributeDescriptionParameters) description.getParameters())
        .forEach(parameters -> {
          var attributeName = parameters.getAttributeName();
          if (graphElement.containsAttribute(attributeName)) {
            list.add(graphElement.getAttribute(attributeName));
          } else {
            try {
              var fieldDefinition = this.structureSchemaFinder.getFieldDefinitionFor(
                  graphElementType,
                  attributeName
              );
              if (fieldDefinition.isList()) {
                list.add(new ListAttribute(attributeName));
              }
            } catch (FieldsNotFoundException ignored) {
              
            }
          }
        });

    return list;
  }

  private Object resolveSearchOptionStep(
      TraversableGraphElement element,
      PositiveGraphDescription graphDescription
  ) {
    if (element instanceof TraversableNode node) {
      return this.resolveNodeSearchOptionStep(
          node,
          (AbstractNodeDescription) graphDescription
      );
    }
    if (element instanceof TraversableEdge edge) {
      return this.resolveEdgeSearchOptionStep(
          edge,
          (AbstractEdgeDescription) graphDescription
      );
    }
    throw new RuntimeException("There should never be any other graph element.");
  }

  private Object resolveNodeSearchOptionStep(
      TraversableNode node,
      AbstractNodeDescription graphDescription
  ) {
    var childDescription = graphDescription.getChildGraphDescriptions().get(0);
    if (childDescription instanceof AbstractEdgeDescription edgeDescription) {
      var edgeParam = (EdgeDescriptionParameters) edgeDescription.getParameters();
      List edges;
      if (edgeDescription.isIngoing()) {
        edges = node.getIngoingEdges(edgeParam.getEdgeType());
      } else {
        edges = node.getOutgoingEdges(edgeParam.getEdgeType());
      }
      SearchQueryParameters searchQueryParameters;
      if (edgeDescription instanceof EdgeQueryDescription edgeQueryDescription) {
        searchQueryParameters = edgeQueryDescription.getSearchQueryParameters();
      } else {
        searchQueryParameters = SearchQueryParameters.from();
      }
      var graphElements = new ArrayList(edges);
      var filteredGraphElements = this.applySearchQueryParameters(
          graphElements,
          searchQueryParameters,
          edgeDescription
      );
      return filteredGraphElements.stream()
          .map(edge -> this.resolveEdgeSearchOptionStep((TraversableEdge) edge, edgeDescription))
          .collect(Collectors.toList());
    }
    if (childDescription instanceof AbstractAttributeDescription attributeDescription) {
      var attributeParam = (AttributeDescriptionParameters) attributeDescription.getParameters();
      if (!node.hasAttribute(attributeParam.getAttributeName())) {
        return null;
      }
      return node.getAttribute(attributeParam.getAttributeName());
    }
    if (childDescription instanceof UuidIdentityDescription) {
      return new LeafAttribute<>("id", new IdAttributeValue(node.getId().getId()));
    }
    throw new RuntimeException("Should not ever happen, already validated.");
  }

  private Object resolveEdgeSearchOptionStep(
      TraversableEdge edge,
      AbstractEdgeDescription graphDescription
  ) {
    var childDescription = graphDescription.getChildGraphDescriptions().get(0);
    if (childDescription instanceof AbstractNodeDescription nodeDescription) {
      var nodeParam = (NodeDescriptionParameters) nodeDescription.getParameters();
      TraversableNode otherNode;
      if (graphDescription.isIngoing()) {
        otherNode = edge.getNodeFrom();
      } else {
        otherNode = edge.getNodeTo();
      }
      if (!otherNode.getType().equals(nodeParam.getNodeType())) {
        return null;
      }
      return this.resolveNodeSearchOptionStep(otherNode, nodeDescription);
    }
    if (childDescription instanceof AbstractAttributeDescription attributeDescription) {
      var attributeParam = (AttributeDescriptionParameters) attributeDescription.getParameters();
      if (!edge.hasAttribute(attributeParam.getAttributeName())) {
        return null;
      }
      return edge.getAttribute(attributeParam.getAttributeName());
    }
    if (childDescription instanceof UuidIdentityDescription) {
      return new LeafAttribute<>("id", new IdAttributeValue(edge.getId().getId()));
    }
    throw new RuntimeException("Should not ever happen, already validated.");
  }

  private boolean isMappingToConnectionObject(
      AbstractEdgeDescription graphDescription,
      Set returnTypes
  ) {
    if (!returnTypes.contains(GraphLoaderReturnType.OBJECT)) {
      return false;
    }
    if (graphDescription instanceof EdgeQueryDescription edgeQueryDescription) {
      return !edgeQueryDescription.isCompact();
    }
    return false;
  }

  private static class GraphLoaderStepOutput {


    private final Map object;
    private final Graph graph;

    public GraphLoaderStepOutput(Map object, Graph graph) {
      this.object = object;
      this.graph = graph;
    }

    public GraphLoaderStepOutput() {
      this(new HashMap<>(), new Graph());
    }

    public Map getObject() {
      return object;
    }

    public Graph getGraph() {
      return graph;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy