From d2c114ae8edd3e9943421a2016da147fcacb905f Mon Sep 17 00:00:00 2001 From: zhoney Date: Thu, 30 Aug 2018 19:52:32 +0800 Subject: [PATCH] add subgraphpaths restful api fix #39 Change-Id: I41f82b73cee0ce657b3bcaaf1a68cf3479788fbf --- hugegraph-api/pom.xml | 2 +- .../hugegraph/api/traversers/PathsAPI.java | 2 +- .../api/traversers/SubGraphPaths.java | 80 +++++++++++ .../hugegraph/serializer/JsonSerializer.java | 31 ++++ .../hugegraph/serializer/Serializer.java | 6 + .../baidu/hugegraph/version/ApiVersion.java | 3 +- .../traversal/optimize/HugeTraverser.java | 132 +++++++++++++++++- 7 files changed, 250 insertions(+), 6 deletions(-) create mode 100644 hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SubGraphPaths.java diff --git a/hugegraph-api/pom.xml b/hugegraph-api/pom.xml index ce8fa2e6bc..0cc83d5bb4 100644 --- a/hugegraph-api/pom.xml +++ b/hugegraph-api/pom.xml @@ -101,7 +101,7 @@ - 0.27.0.0 + 0.28.0.0 diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/PathsAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/PathsAPI.java index ab64b8b2b3..dd08312933 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/PathsAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/PathsAPI.java @@ -80,4 +80,4 @@ public String get(@Context GraphManager manager, edgeLabel, maxDepth, degree, capacity, limit); return manager.serializer(g).writePaths("paths", paths, false); } -} \ No newline at end of file +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SubGraphPaths.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SubGraphPaths.java new file mode 100644 index 0000000000..4db8ae3085 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SubGraphPaths.java @@ -0,0 +1,80 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.baidu.hugegraph.api.traversers; + +import javax.inject.Singleton; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MultivaluedMap; + +import org.slf4j.Logger; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.api.API; +import com.baidu.hugegraph.api.graph.EdgeAPI; +import com.baidu.hugegraph.api.graph.VertexAPI; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.core.GraphManager; +import com.baidu.hugegraph.server.RestServer; +import com.baidu.hugegraph.traversal.optimize.HugeTraverser; +import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.Log; +import com.codahale.metrics.annotation.Timed; + +@Path("graphs/{graph}/traversers/subgraphpaths") +@Singleton +public class SubGraphPaths extends API { + + private static final Logger LOG = Log.logger(RestServer.class); + + @GET + @Timed + @Produces(APPLICATION_JSON_WITH_CHARSET) + public String get(@Context GraphManager manager, + @PathParam("graph") String graph, + @QueryParam("source") String sourceV, + @QueryParam("direction") String direction, + @QueryParam("label") String edgeLabel, + @QueryParam("depth") int depth, + @QueryParam("degree") @DefaultValue("-1") long degree, + @QueryParam("capacity") @DefaultValue("-1") long capacity, + @QueryParam("limit") @DefaultValue("-1") long limit) { + LOG.debug("Graph [{}] get sub-graph paths from '{}' with " + + "direction '{}', edge label '{}', depth '{}', " + + "degree '{}' and limit '{}'", + graph, sourceV, direction, edgeLabel, depth, degree, limit); + + Id source = VertexAPI.checkAndParseVertexId(sourceV); + Directions dir = Directions.convert(EdgeAPI.parseDirection(direction)); + + HugeGraph g = graph(manager, graph); + + HugeTraverser traverser = new HugeTraverser(g); + MultivaluedMap paths = traverser.subGraphPaths(source, dir, edgeLabel, + depth, degree, capacity, + limit); + return manager.serializer(g).writeSubGraphPaths(paths, false); + } +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/JsonSerializer.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/JsonSerializer.java index 15641d9063..ff849932bf 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/JsonSerializer.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/JsonSerializer.java @@ -20,11 +20,15 @@ package com.baidu.hugegraph.serializer; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; + +import javax.ws.rs.core.MultivaluedMap; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; import org.apache.tinkerpop.gremlin.structure.Edge; @@ -194,6 +198,33 @@ public String writePaths(String name, Collection paths, return writeList(name, pathList); } + @Override + public String writeSubGraphPaths( + MultivaluedMap paths, + boolean withCrossPoint) { + try (ByteArrayOutputStream out = new ByteArrayOutputStream(LBUF_SIZE)) { + out.write("{\"loop path\": ".getBytes(API.CHARSET)); + this.writePathsList(out, paths.get(true)); + out.write(",\"leaf path\": ".getBytes(API.CHARSET)); + this.writePathsList(out, paths.get(false)); + out.write("}".getBytes(API.CHARSET)); + return out.toString(API.CHARSET); + } catch (Exception e) { + throw new HugeException("Failed to serialize sub-graph-paths", e); + } + } + + private void writePathsList(ByteArrayOutputStream out, + List paths) + throws IOException { + if (paths == null) { + out.write("[]".getBytes(API.CHARSET)); + } else { + this.writer.writeObject(out, paths.stream().map(p -> p.toMap(false)) + .collect(Collectors.toList())); + } + } + @Override public String writeShards(List shards) { return this.writeList("shards", shards); diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/Serializer.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/Serializer.java index 352182655a..aa5d1be069 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/Serializer.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/Serializer.java @@ -23,6 +23,8 @@ import java.util.Iterator; import java.util.List; +import javax.ws.rs.core.MultivaluedMap; + import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Vertex; @@ -65,5 +67,9 @@ public interface Serializer { public String writePaths(String name, Collection paths, boolean withCrossPoint); + public String writeSubGraphPaths( + MultivaluedMap paths, + boolean withCrossPoint); + public String writeShards(List shards); } diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java index a52be3ea42..c1489af9f6 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java @@ -69,10 +69,11 @@ public final class ApiVersion { * [0.26] HugeGraph-1273: Add some monitoring counters to integrate with * gremlin's monitoring framework * [0.27] HugeGraph-889: Use asynchronous mechanism to do schema deletion + * [0.28] Add subgraphpaths RESTful API */ // The second parameter of Version.of() is for IDE running without JAR - public static final Version VERSION = Version.of(ApiVersion.class, "0.27"); + public static final Version VERSION = Version.of(ApiVersion.class, "0.28"); public static final void check() { // Check version of hugegraph-core. Firstly do check from version 0.3 diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/optimize/HugeTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/optimize/HugeTraverser.java index a258419b4f..4d3210885c 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/optimize/HugeTraverser.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/optimize/HugeTraverser.java @@ -138,6 +138,44 @@ public Set paths(Id sourceV, Directions sourceDir, return paths; } + public MultivaluedMap subGraphPaths( + Id sourceV, Directions dir, + String label, int maxDepth, + long degree, long capacity, + long limit) { + E.checkNotNull(sourceV, "source vertex id"); + E.checkNotNull(dir, "direction"); + checkPositive(maxDepth, "Paths max depth"); + checkDegree(degree); + checkCapacity(capacity); + checkLimit(limit); + + MultivaluedMap subGraphpaths = newMultivalueMap(); + Id labelId = this.getEdgeLabelId(label); + + SubGraphTraverser traverser = new SubGraphTraverser(sourceV, labelId, + degree, capacity, + limit); + while (true) { + Map> foundPaths = traverser.forward(dir); + // Add loop paths + List paths = foundPaths.get(true); + if (paths != null && !paths.isEmpty()) { + subGraphpaths.addAll(true, paths); + } + // Add leaf paths + paths = foundPaths.get(false); + if (paths != null && !paths.isEmpty()) { + subGraphpaths.addAll(false, paths); + } + if (--maxDepth < 0 || traverser.reachLimit() || + traverser.finished()) { + break; + } + } + return subGraphpaths; + } + public Set kout(Id sourceV, Directions dir, String label, int depth, boolean nearest, long degree, long capacity, long limit) { @@ -408,7 +446,7 @@ public List backward() { return PATH_NONE; } - public boolean reachCapacity() { + private boolean reachCapacity() { if (this.capacity == NO_LIMIT || this.size < this.capacity) { return false; } @@ -539,11 +577,11 @@ public List backward(Directions direction) { return paths; } - public int accessedNodes() { + private int accessedNodes() { return this.sourcesAll.size() + this.targetsAll.size(); } - public boolean reachLimit() { + private boolean reachLimit() { if (this.capacity != NO_LIMIT && this.accessedNodes() > this.capacity) { return true; @@ -555,6 +593,94 @@ public boolean reachLimit() { } } + private class SubGraphTraverser { + + private MultivaluedMap sources = newMultivalueMap(); + private Set accessedVertices = newSet(); + + private final Id label; + private final long degree; + private final long capacity; + private final long limit; + private long pathCount; + + public SubGraphTraverser(Id sourceV, Id label, + long degree, long capacity, long limit) { + this.sources.add(sourceV, new Node(sourceV)); + this.accessedVertices.add(sourceV); + this.label = label; + this.degree = degree; + this.capacity = capacity; + this.limit = limit; + this.pathCount = 0L; + } + + /** + * Search forward from source + */ + public Map> forward(Directions direction) { + MultivaluedMap paths = newMultivalueMap(); + MultivaluedMap newVertices = newMultivalueMap(); + Iterator edges; + // Traversal vertices of previous level + for (List nodes : this.sources.values()) { + for (Node n : nodes) { + edges = edgesOfVertex(n.id(), direction, + this.label, this.degree); + if (!edges.hasNext()) { + // Key 'false' means leaf path without loop + paths.add(false, new Path(null, n.path())); + this.pathCount++; + if (reachLimit()) { + return paths; + } + continue; + } + while (edges.hasNext()) { + HugeEdge edge = (HugeEdge) edges.next(); + Id target = edge.id().otherVertexId(); + this.accessedVertices.add(target); + + // If have loop, add a loop path + if (n.contains(target)) { + List prePath = n.path(); + prePath.add(target); + // Key 'true' means loop path + paths.add(true, new Path(null, prePath)); + this.pathCount++; + if (reachLimit()) { + return paths; + } + continue; + } + + // Add node to next start-nodes + newVertices.add(target, new Node(target, n)); + } + } + } + // Re-init sources + this.sources = newVertices; + + return paths; + } + + private boolean reachLimit() { + if (this.capacity != NO_LIMIT && + this.accessedVertices.size() > this.capacity) { + return true; + } + if (this.limit == NO_LIMIT || this.pathCount < this.limit) { + return false; + } + return true; + } + + private boolean finished() { + return this.sources.isEmpty(); + } + } + private static class Node { private Id id;