diff --git a/hugegraph-api/pom.xml b/hugegraph-api/pom.xml index 0cc83d5bb4..a78ffe3263 100644 --- a/hugegraph-api/pom.xml +++ b/hugegraph-api/pom.xml @@ -101,7 +101,7 @@ - 0.28.0.0 + 0.29.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/Rays.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Rays.java new file mode 100644 index 0000000000..cde50b8243 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Rays.java @@ -0,0 +1,81 @@ +/* + * 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 java.util.List; + +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 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/rays") +@Singleton +public class Rays 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 rays 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); + List paths = traverser.rays(source, dir, edgeLabel, + depth, degree, capacity, + limit); + return manager.serializer(g).writePaths("rays", paths, false); + } +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Rings.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Rings.java new file mode 100644 index 0000000000..a9ca719bbf --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Rings.java @@ -0,0 +1,81 @@ +/* + * 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 java.util.List; + +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 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/rings") +@Singleton +public class Rings 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 rings paths reachable 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); + List paths = traverser.rings(source, dir, edgeLabel, + depth, degree, + capacity, limit); + return manager.serializer(g).writePaths("rings", paths, false); + } +} 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 16cd7fabea..a768246da9 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 @@ -72,10 +72,11 @@ public final class ApiVersion { * * version 0.8: * [0.28] Issue-153: Add task-cancel API + * [0.29] Issue-39: Add rays and rings RESTful API */ // The second parameter of Version.of() is for IDE running without JAR - public static final Version VERSION = Version.of(ApiVersion.class, "0.28"); + public static final Version VERSION = Version.of(ApiVersion.class, "0.29"); 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 e65aea263c..03d7822e09 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,45 @@ public Set paths(Id sourceV, Directions sourceDir, return paths; } + public List rays(Id sourceV, Directions dir, String label, + int maxDepth, long degree, long capacity, + long limit) { + return this.subGraphPaths(sourceV, dir, label, maxDepth, degree, + capacity, limit, false); + } + + public List rings(Id sourceV, Directions dir, String label, + int maxDepth, long degree, long capacity, + long limit) { + return this.subGraphPaths(sourceV, dir, label, maxDepth, degree, + capacity, limit, true); + } + + private List subGraphPaths(Id sourceV, Directions dir, String label, + int maxDepth, long degree, long capacity, + long limit, boolean rings) { + E.checkNotNull(sourceV, "source vertex id"); + E.checkNotNull(dir, "direction"); + checkPositive(maxDepth, "Paths max depth"); + checkDegree(degree); + checkCapacity(capacity); + checkLimit(limit); + + Id labelId = this.getEdgeLabelId(label); + SubGraphTraverser traverser = new SubGraphTraverser(sourceV, labelId, + degree, capacity, + limit, rings); + List paths = new ArrayList<>(); + while (true) { + paths.addAll(traverser.forward(dir)); + if (--maxDepth < 0 || traverser.reachLimit() || + traverser.finished()) { + break; + } + } + return paths; + } + public Set kout(Id sourceV, Directions dir, String label, int depth, boolean nearest, long degree, long capacity, long limit) { @@ -408,7 +447,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 +578,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 +594,101 @@ 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 final boolean rings; + private long pathCount; + + public SubGraphTraverser(Id sourceV, Id label, long degree, + long capacity, long limit, boolean rings) { + this.sources.add(sourceV, new Node(sourceV)); + this.accessedVertices.add(sourceV); + this.label = label; + this.degree = degree; + this.capacity = capacity; + this.limit = limit; + this.rings = rings; + this.pathCount = 0L; + } + + /** + * Search forward from source + */ + public List forward(Directions direction) { + List paths = new ArrayList<>(); + 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()) { + // Reach the end, rays found + if (this.rings) { + continue; + } + // Store rays + paths.add(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 (!n.contains(target)) { + // Add node to next start-nodes + newVertices.add(target, new Node(target, n)); + continue; + } + // Rings found and expect rings + if (this.rings) { + assert n.contains(target); + List prePath = n.path(); + prePath.add(target); + paths.add(new Path(null, prePath)); + this.pathCount++; + if (reachLimit()) { + return paths; + } + } + } + } + } + // 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;