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;