Cypher(Neo4j):读取在具有特定属性

时间:2015-11-22 17:09:19

标签: java json neo4j tree cypher

我有这样的树结构:

enter image description here

每个节点都有id属性,例如' root',' child0'等等,expanded属性意味着当前分支已展开,即查询时应包含其子代。

所以,我需要的是编写Cypher查询,从具有指定id的节点开始,读取其所有子节点停止在叶子和expanded属性设置为{的节点{1}}。

结果应该是树状的,所以我不需要进一步处理它。

我试过像

这样的东西
false

然后我得到了深度优先'路径(带有一堆重复的信息),我需要进行后处理以从中获取树。我无法做到这一点,因为它没有包含儿童的订购信息( NEXT_SIBLING_OF 属性用于什么)。

是否有一种惯用的方法可以在Cypher中实现这一点,还是应该回退到编写非托管扩展?

1 个答案:

答案 0 :(得分:0)

现在我已经使用非托管扩展实现了我需要的东西,看起来几乎不可能在Cypher中正确快速地完成它。

package io.treev.treegraph.neo.extension;

import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.ObjectMapper;
import org.neo4j.graphdb.*;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.helpers.collection.IteratorUtil;

import javax.ws.rs.GET;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import java.io.IOException;
import java.util.Optional;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

@javax.ws.rs.Path("/")
public class TreeGraphResource {

  private final GraphDatabaseService db;
  private final ObjectMapper objectMapper;

  public TreeGraphResource(@Context GraphDatabaseService graphDb) {
    this.db = graphDb;
    this.objectMapper = new ObjectMapper();
  }

  @GET
  @javax.ws.rs.Path("/{nodeId}")
  @Produces(MediaType.APPLICATION_JSON)
  public Response readTree(@PathParam("nodeId") String nodeId) {
    StreamingOutput stream = output -> {
      JsonGenerator jg = objectMapper.getJsonFactory().createJsonGenerator(output, JsonEncoding.UTF8);

      try (Transaction ignore = db.beginTx()) {
        Node node = getNode(nodeId);
        buildNode(node, jg);
      }

      jg.flush();
      jg.close();
    };

    return Response.ok().entity(stream).type(MediaType.APPLICATION_JSON).build();
  }

  private static final String TREE_NODE_LABEL = "TreeNode";

  private static final String ID_PROPERTY = "id";
  private static final String EXPANDED_PROPERTY = "expanded";
  private static final String CHILDREN_PROPERTY = "children";

  private static final DynamicRelationshipType CHILD_OF_REL =
    DynamicRelationshipType.withName("CHILD_OF");
  private static final DynamicRelationshipType NEXT_SIBLING_OF_REL =
    DynamicRelationshipType.withName("NEXT_SIBLING_OF");

  private Node getNode(String nodeId) {
    return db.findNode(DynamicLabel.label(TREE_NODE_LABEL), ID_PROPERTY, nodeId);
  }

  private void buildNode(Node node, JsonGenerator jg) {
    try {
      jg.writeStartObject();

      jg.writeFieldName(ID_PROPERTY);
      jg.writeString((String) node.getProperty(ID_PROPERTY));

      Optional<Boolean> expandedOpt = isExpanded(node);
      if (expandedOpt.isPresent()) {
        boolean expanded = expandedOpt.get();

        jg.writeFieldName(EXPANDED_PROPERTY);
        jg.writeBoolean(expanded);

        if (!expanded) {
          int childrenCount = countChildren(node);

          if (childrenCount > 0) {
            jg.writeFieldName("childrenCount");
            jg.writeNumber(childrenCount);
          }
        }
      }

      if (!expandedOpt.isPresent() || expandedOpt.get()) {
        Stream<Node> children = getChildren(node);

        if (children != null) {
          jg.writeFieldName(CHILDREN_PROPERTY);
          jg.writeStartArray();

          children.forEach(child -> buildNode(child, jg));

          jg.writeEndArray();
        }
      }

      jg.writeEndObject();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  private Stream<Node> getChildren(Node node) {
    if (node != null) {
      Optional<Node> firstChildOpt = getFirstChild(node);

      return firstChildOpt.map(firstChild -> {
        TraversalDescription traversal =
          db.traversalDescription().depthFirst().relationships(NEXT_SIBLING_OF_REL, Direction.INCOMING);

        return StreamSupport.stream(traversal.traverse(firstChild).spliterator(), false).map(Path::endNode);
      }).orElse(null);

    } else {
      return null;
    }
  }

  private Optional<Node> getFirstChild(Node node) {
    Iterable<Relationship> childrenRelations = node.getRelationships(CHILD_OF_REL, Direction.INCOMING);

    return StreamSupport.stream(childrenRelations.spliterator(), false)
      .map(Relationship::getStartNode)
      .filter(relationship -> !relationship.hasRelationship(NEXT_SIBLING_OF_REL, Direction.OUTGOING))
      .findFirst();
  }

  private int countChildren(Node node) {
    if (node != null) {
      return IteratorUtil.count(node.getRelationships(CHILD_OF_REL, Direction.INCOMING));
    } else {
      return 0;
    }
  }

  private Optional<Boolean> isExpanded(Node node) {
    Boolean expanded = (Boolean) node.getProperty(EXPANDED_PROPERTY, null);
    return Optional.ofNullable(expanded);
  }

}

回复示例:

{
  "id": "root",
  "expanded": true,
  "children": [
    {
      "id": "child10"
    },
    {
      "id": "child0"
    },
    {
      "id": "child1",
      "expanded": true,
      "children": [
        {
          "id": "child2",
          "expanded": false,
          "childrenCount": 1
        },
        {
          "id": "child3"
        },
        {
          "id": "child4"
        }
      ]
    }
  ]
}