diff --git a/hugegraph-api/pom.xml b/hugegraph-api/pom.xml index a2a3e2f8cd..fa2fbefe9a 100644 --- a/hugegraph-api/pom.xml +++ b/hugegraph-api/pom.xml @@ -104,7 +104,7 @@ - 0.57.0.0 + 0.58.0.0 diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/AllShortestPathsAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/AllShortestPathsAPI.java index 1f88ad4654..941042893e 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/AllShortestPathsAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/AllShortestPathsAPI.java @@ -19,6 +19,8 @@ package com.baidu.hugegraph.api.traversers; +import java.util.List; + import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; @@ -45,6 +47,7 @@ import com.baidu.hugegraph.type.define.Directions; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; +import com.google.common.collect.ImmutableList; @Path("graphs/{graph}/traversers/allshortestpaths") @Singleton @@ -81,9 +84,11 @@ public String get(@Context GraphManager manager, HugeGraph g = graph(manager, graph); ShortestPathTraverser traverser = new ShortestPathTraverser(g); + List edgeLabels = edgeLabel == null ? ImmutableList.of() : + ImmutableList.of(edgeLabel); HugeTraverser.PathSet paths = traverser.allShortestPaths( - sourceId, targetId, dir, edgeLabel, depth, - degree, skipDegree, capacity); + sourceId, targetId, dir, edgeLabels, + depth, degree, skipDegree, capacity); return manager.serializer(g).writePaths("paths", paths, false); } } diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedCrosspointsAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedCrosspointsAPI.java index 26a4cf00de..293905a9fd 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedCrosspointsAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedCrosspointsAPI.java @@ -84,7 +84,7 @@ public String post(@Context GraphManager manager, request.capacity, request.limit); HugeGraph g = graph(manager, graph); - Iterator sources = request.sources.sourcesVertices(g); + Iterator sources = request.sources.vertices(g); List patterns; patterns = pathPatterns(g, request); @@ -132,7 +132,7 @@ public String post(@Context GraphManager manager, private static class CrosspointsRequest { @JsonProperty("sources") - public SourceVertices sources; + public Vertices sources; @JsonProperty("path_patterns") public List pathPatterns; @JsonProperty("capacity") diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedPathsAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedPathsAPI.java index 8dfac69cd2..e49f808e0c 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedPathsAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/CustomizedPathsAPI.java @@ -86,7 +86,7 @@ public String post(@Context GraphManager manager, request.withVertex); HugeGraph g = graph(manager, graph); - Iterator sources = request.sources.sourcesVertices(g); + Iterator sources = request.sources.vertices(g); List steps = step(g, request); boolean sorted = request.sortBy != SortBy.NONE; @@ -129,7 +129,7 @@ private static List step(HugeGraph graph, private static class PathRequest { @JsonProperty("sources") - public SourceVertices sources; + public Vertices sources; @JsonProperty("steps") public List steps; @JsonProperty("sort_by") diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/FusiformSimilarityAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/FusiformSimilarityAPI.java index 0a56f56b82..e6ef2858b7 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/FusiformSimilarityAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/FusiformSimilarityAPI.java @@ -97,7 +97,7 @@ public String post(@Context GraphManager manager, request.groupProperty, request.minGroups); HugeGraph g = graph(manager, graph); - Iterator sources = request.sources.sourcesVertices(g); + Iterator sources = request.sources.vertices(g); E.checkArgument(sources != null && sources.hasNext(), "The source vertices can't be empty"); EdgeLabel edgeLabel = request.label == null ? @@ -125,7 +125,7 @@ public String post(@Context GraphManager manager, private static class FusiformSimilarityRequest { @JsonProperty("sources") - public SourceVertices sources; + public Vertices sources; @JsonProperty("label") public String label; @JsonProperty("direction") diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/JaccardSimilarityAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/JaccardSimilarityAPI.java index 5c2449ce8d..c7ee87525f 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/JaccardSimilarityAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/JaccardSimilarityAPI.java @@ -19,11 +19,17 @@ package com.baidu.hugegraph.api.traversers; +import java.util.Map; + +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_LIMIT; import javax.inject.Singleton; +import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; +import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; @@ -33,22 +39,25 @@ 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.algorithm.HugeTraverser; +import com.baidu.hugegraph.structure.HugeVertex; +import com.baidu.hugegraph.traversal.algorithm.EdgeStep; +import com.baidu.hugegraph.traversal.algorithm.JaccardSimilarTraverser; import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.E; import com.baidu.hugegraph.util.JsonUtil; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableMap; @Path("graphs/{graph}/traversers/jaccardsimilarity") @Singleton -public class JaccardSimilarityAPI extends API { +public class JaccardSimilarityAPI extends TraverserAPI { private static final Logger LOG = Log.logger(RestServer.class); @@ -72,10 +81,61 @@ public String get(@Context GraphManager manager, Directions dir = Directions.convert(EdgeAPI.parseDirection(direction)); HugeGraph g = graph(manager, graph); - HugeTraverser traverser = new HugeTraverser(g); + JaccardSimilarTraverser traverser = new JaccardSimilarTraverser(g); double similarity = traverser.jaccardSimilarity(sourceId, targetId, dir, edgeLabel, degree); return JsonUtil.toJson(ImmutableMap.of("jaccard_similarity", similarity)); } + + @POST + @Timed + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public String post(@Context GraphManager manager, + @PathParam("graph") String graph, + Request request) { + E.checkArgumentNotNull(request, "The request body can't be null"); + E.checkArgumentNotNull(request.vertex, + "The source vertex of request can't be null"); + E.checkArgument(request.step != null, + "The steps of request can't be null"); + E.checkArgument(request.top >= 0, + "The top must be >= 0, but got: %s", request.top); + + LOG.debug("Graph [{}] get jaccard similars from source vertex '{}', " + + "with step '{}', top '{}' and capacity '{}'", + graph, request.vertex, request.step, + request.top, request.capacity); + + HugeGraph g = graph(manager, graph); + Id sourceId = HugeVertex.getIdValue(request.vertex); + + EdgeStep step = step(g, request.step); + + JaccardSimilarTraverser traverser = new JaccardSimilarTraverser(g); + Map results = traverser.jaccardSimilars(sourceId, step, + request.top, + request.capacity); + return manager.serializer(g).writeMap(results); + } + + private static class Request { + + @JsonProperty("vertex") + public Object vertex; + @JsonProperty("step") + public TraverserAPI.Step step; + @JsonProperty("top") + public int top = Integer.valueOf(DEFAULT_LIMIT); + @JsonProperty("capacity") + public long capacity = Long.valueOf(DEFAULT_CAPACITY); + + @Override + public String toString() { + return String.format("Request{vertex=%s,step=%s,top=%s," + + "capacity=%s}", this.vertex, this.step, + this.top, this.capacity); + } + } } diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/KneighborAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/KneighborAPI.java index 16ddb0b009..77822e7c3b 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/KneighborAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/KneighborAPI.java @@ -21,35 +21,48 @@ import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_ELEMENTS_LIMIT; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.Set; import javax.inject.Singleton; +import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; +import javax.ws.rs.POST; 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.apache.tinkerpop.gremlin.structure.Vertex; 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.backend.query.QueryResults; import com.baidu.hugegraph.core.GraphManager; import com.baidu.hugegraph.server.RestServer; +import com.baidu.hugegraph.structure.HugeVertex; +import com.baidu.hugegraph.traversal.algorithm.KneighborTraverser; +import com.baidu.hugegraph.traversal.algorithm.EdgeStep; import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.E; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.annotation.JsonProperty; @Path("graphs/{graph}/traversers/kneighbor") @Singleton -public class KneighborAPI extends API { +public class KneighborAPI extends TraverserAPI { private static final Logger LOG = Log.logger(RestServer.class); @@ -77,9 +90,98 @@ public String get(@Context GraphManager manager, HugeGraph g = graph(manager, graph); - HugeTraverser traverser = new HugeTraverser(g); + KneighborTraverser traverser = new KneighborTraverser(g); Set ids = traverser.kneighbor(source, dir, edgeLabel, depth, degree, limit); return manager.serializer(g).writeList("vertices", ids); } + + @POST + @Timed + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public String post(@Context GraphManager manager, + @PathParam("graph") String graph, + Request request) { + E.checkArgumentNotNull(request, "The request body can't be null"); + E.checkArgumentNotNull(request.source, + "The source of request can't be null"); + E.checkArgument(request.step != null, + "The steps of request can't be null"); + if (request.countOnly) { + E.checkArgument(!request.withVertex && !request.withPath, + "Can't return vertex or path when count only"); + } + + LOG.debug("Graph [{}] get customized kneighbor from source vertex " + + "'{}', with step '{}', limit '{}', count_only '{}', " + + "with_vertex '{}' and with_path '{}'", + graph, request.source, request.step, request.limit, + request.countOnly, request.withVertex, request.withPath); + + HugeGraph g = graph(manager, graph); + Id sourceId = HugeVertex.getIdValue(request.source); + + EdgeStep step = step(g, request.step); + + KneighborTraverser traverser = new KneighborTraverser(g); + Set results = traverser.customizedKneighbor( + sourceId, step, request.maxDepth, + request.limit); + + Set neighbors = new HashSet<>(); + for (HugeTraverser.Node node : results) { + neighbors.add(node.id()); + } + + List paths = new ArrayList<>(); + if (request.withPath) { + for (HugeTraverser.Node node : results) { + paths.add(new HugeTraverser.Path(node.path())); + } + } + Iterator iter = QueryResults.emptyIterator(); + if (request.withVertex) { + Set ids = new HashSet<>(); + for (HugeTraverser.Node node : results) { + ids.add(node.id()); + } + for (HugeTraverser.Path p : paths) { + ids.addAll(p.vertices()); + } + if (!ids.isEmpty()) { + iter = g.vertices(ids.toArray()); + } + } + return manager.serializer(g).writeNodesWithPath("kneighbor", neighbors, + paths, iter, + request.countOnly); + } + + private static class Request { + + @JsonProperty("source") + public Object source; + @JsonProperty("step") + public TraverserAPI.Step step; + @JsonProperty("max_depth") + public int maxDepth; + @JsonProperty("limit") + public long limit = Long.valueOf(DEFAULT_PATHS_LIMIT); + @JsonProperty("count_only") + public boolean countOnly = false; + @JsonProperty("with_vertex") + public boolean withVertex = false; + @JsonProperty("with_path") + public boolean withPath = false; + + @Override + public String toString() { + return String.format("PathRequest{source=%s,step=%s,maxDepth=%s" + + "limit=%s,countOnly=%s,withVertex=%s," + + "withPath=%s}", this.source, this.step, + this.maxDepth, this.limit, this.countOnly, + this.withVertex, this.withPath); + } + } } diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/KoutAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/KoutAPI.java index 6f15c1944b..eb7aa401f8 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/KoutAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/KoutAPI.java @@ -19,38 +19,52 @@ package com.baidu.hugegraph.api.traversers; -import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; -import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; -import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_ELEMENTS_LIMIT; - +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.Set; import javax.inject.Singleton; +import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; +import javax.ws.rs.POST; 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.apache.tinkerpop.gremlin.structure.Vertex; 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.backend.query.QueryResults; import com.baidu.hugegraph.core.GraphManager; import com.baidu.hugegraph.server.RestServer; +import com.baidu.hugegraph.structure.HugeVertex; +import com.baidu.hugegraph.traversal.algorithm.KoutTraverser; +import com.baidu.hugegraph.traversal.algorithm.EdgeStep; import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.E; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.annotation.JsonProperty; + +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.Node; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_ELEMENTS_LIMIT; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT; @Path("graphs/{graph}/traversers/kout") @Singleton -public class KoutAPI extends API { +public class KoutAPI extends TraverserAPI { private static final Logger LOG = Log.logger(RestServer.class); @@ -82,9 +96,106 @@ public String get(@Context GraphManager manager, HugeGraph g = graph(manager, graph); - HugeTraverser traverser = new HugeTraverser(g); + KoutTraverser traverser = new KoutTraverser(g); Set ids = traverser.kout(sourceId, dir, edgeLabel, depth, nearest, degree, capacity, limit); return manager.serializer(g).writeList("vertices", ids); } + + @POST + @Timed + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public String post(@Context GraphManager manager, + @PathParam("graph") String graph, + Request request) { + E.checkArgumentNotNull(request, "The request body can't be null"); + E.checkArgumentNotNull(request.source, + "The source of request can't be null"); + E.checkArgument(request.step != null, + "The steps of request can't be null"); + if (request.countOnly) { + E.checkArgument(!request.withVertex && !request.withPath, + "Can't return vertex or path when count only"); + } + + LOG.debug("Graph [{}] get customized kout from source vertex '{}', " + + "with step '{}', max_depth '{}', nearest '{}', " + + "count_only '{}', capacity '{}', limit '{}', " + + "with_vertex '{}' and with_path '{}'", + graph, request.source, request.step, request.maxDepth, + request.nearest, request.countOnly, request.capacity, + request.limit, request.withVertex, request.withPath); + + HugeGraph g = graph(manager, graph); + Id sourceId = HugeVertex.getIdValue(request.source); + + EdgeStep step = step(g, request.step); + + KoutTraverser traverser = new KoutTraverser(g); + Set results = traverser.customizedKout( + sourceId, step, request.maxDepth, + request.nearest, request.capacity, + request.limit); + + Set neighbors = new HashSet<>(); + for (HugeTraverser.Node node : results) { + neighbors.add(node.id()); + } + + List paths = new ArrayList<>(); + if (request.withPath) { + for (HugeTraverser.Node node : results) { + paths.add(new HugeTraverser.Path(node.path())); + } + } + Iterator iter = QueryResults.emptyIterator(); + if (request.withVertex) { + Set ids = new HashSet<>(); + for (Node node : results) { + ids.add(node.id()); + } + for (HugeTraverser.Path p : paths) { + ids.addAll(p.vertices()); + } + if (!ids.isEmpty()) { + iter = g.vertices(ids.toArray()); + } + } + return manager.serializer(g).writeNodesWithPath("kout", neighbors, + paths, iter, + request.countOnly); + } + + private static class Request { + + @JsonProperty("source") + public Object source; + @JsonProperty("step") + public TraverserAPI.Step step; + @JsonProperty("max_depth") + public int maxDepth; + @JsonProperty("nearest") + public boolean nearest = true; + @JsonProperty("count_only") + public boolean countOnly = false; + @JsonProperty("capacity") + public long capacity = Long.valueOf(DEFAULT_CAPACITY); + @JsonProperty("limit") + public long limit = Long.valueOf(DEFAULT_PATHS_LIMIT); + @JsonProperty("with_vertex") + public boolean withVertex = false; + @JsonProperty("with_path") + public boolean withPath = false; + + @Override + public String toString() { + return String.format("KoutRequest{source=%s,step=%s,maxDepth=%s" + + "nearest=%s,countOnly=%s,capacity=%s," + + "limit=%s,withVertex=%s,withPath=%s}", + this.source, this.step, this.maxDepth, + this.nearest, this.countOnly, this.capacity, + this.limit, this.withVertex, this.withPath); + } + } } diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/MultiNodeShortestPathAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/MultiNodeShortestPathAPI.java new file mode 100644 index 0000000000..c3cebec720 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/MultiNodeShortestPathAPI.java @@ -0,0 +1,126 @@ +/* + * 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.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.inject.Singleton; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; + +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.backend.query.QueryResults; +import com.baidu.hugegraph.core.GraphManager; +import com.baidu.hugegraph.server.RestServer; +import com.baidu.hugegraph.traversal.algorithm.EdgeStep; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.MultiNodeShortestPathTraverser; +import com.baidu.hugegraph.util.E; +import com.baidu.hugegraph.util.Log; +import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.annotation.JsonProperty; + +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; + +@Path("graphs/{graph}/traversers/multinodeshortestpath") +@Singleton +public class MultiNodeShortestPathAPI extends TraverserAPI { + + private static final Logger LOG = Log.logger(RestServer.class); + + @POST + @Timed + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public String post(@Context GraphManager manager, + @PathParam("graph") String graph, + Request request) { + E.checkArgumentNotNull(request, "The request body can't be null"); + E.checkArgumentNotNull(request.vertices, + "The vertices of request can't be null"); + E.checkArgument(request.step != null, + "The steps of request can't be null"); + + LOG.debug("Graph [{}] get multiple node shortest path from " + + "vertices '{}', with step '{}', max_depth '{}', capacity " + + "'{}' and with_vertex '{}'", + graph, request.vertices, request.step, request.maxDepth, + request.capacity, request.withVertex); + + HugeGraph g = graph(manager, graph); + Iterator vertices = request.vertices.vertices(g); + + EdgeStep step = step(g, request.step); + + MultiNodeShortestPathTraverser traverser = + new MultiNodeShortestPathTraverser(g); + List paths; + paths = traverser.multiNodeShortestPath(vertices, step, + request.maxDepth, + request.capacity); + + if (!request.withVertex) { + return manager.serializer(g).writePaths("paths", paths, false); + } + + Set ids = new HashSet<>(); + for (HugeTraverser.Path p : paths) { + ids.addAll(p.vertices()); + } + Iterator iter = QueryResults.emptyIterator(); + if (!ids.isEmpty()) { + iter = g.vertices(ids.toArray()); + } + return manager.serializer(g).writePaths("paths", paths, false, iter); + } + + private static class Request { + + @JsonProperty("vertices") + public Vertices vertices; + @JsonProperty("step") + public Step step; + @JsonProperty("max_depth") + public int maxDepth; + @JsonProperty("capacity") + public long capacity = Long.valueOf(DEFAULT_CAPACITY); + @JsonProperty("with_vertex") + public boolean withVertex = false; + + @Override + public String toString() { + return String.format("Request{vertices=%s,step=%s,maxDepth=%s" + + "capacity=%s,withVertex=%s}", + this.vertices, this.step, this.maxDepth, + this.capacity, this.withVertex); + } + } +} 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 e54367fcee..27ea55b2eb 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 @@ -19,37 +19,49 @@ package com.baidu.hugegraph.api.traversers; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT; import javax.inject.Singleton; +import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; +import javax.ws.rs.POST; 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.apache.tinkerpop.gremlin.structure.Vertex; 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.backend.query.QueryResults; import com.baidu.hugegraph.core.GraphManager; import com.baidu.hugegraph.server.RestServer; +import com.baidu.hugegraph.traversal.algorithm.CollectionPathsTraverser; +import com.baidu.hugegraph.traversal.algorithm.EdgeStep; import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; import com.baidu.hugegraph.traversal.algorithm.PathsTraverser; import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.E; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.annotation.JsonProperty; @Path("graphs/{graph}/traversers/paths") @Singleton -public class PathsAPI extends API { +public class PathsAPI extends TraverserAPI { private static final Logger LOG = Log.logger(RestServer.class); @@ -87,4 +99,85 @@ public String get(@Context GraphManager manager, limit); return manager.serializer(g).writePaths("paths", paths, false); } + + @POST + @Timed + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public String post(@Context GraphManager manager, + @PathParam("graph") String graph, + Request request) { + E.checkArgumentNotNull(request, "The request body can't be null"); + E.checkArgumentNotNull(request.sources, + "The sources of request can't be null"); + E.checkArgumentNotNull(request.targets, + "The targets of request can't be null"); + E.checkArgumentNotNull(request.step, + "The step of request can't be null"); + E.checkArgument(request.depth > 0, + "The depth of request must be > 0, but got: %s", + request.depth); + + LOG.debug("Graph [{}] get paths from source vertices '{}', target " + + "vertices '{}', with step '{}', max depth '{}', " + + "capacity '{}', limit '{}' and with_vertex '{}'", + graph, request.sources, request.targets, request.step, + request.depth, request.capacity, request.limit, + request.withVertex); + + HugeGraph g = graph(manager, graph); + Iterator sources = request.sources.vertices(g); + Iterator targets = request.targets.vertices(g); + EdgeStep step = step(g, request.step); + + CollectionPathsTraverser traverser = new CollectionPathsTraverser(g); + Collection paths; + paths = traverser.paths(sources, targets, step, request.depth, + request.nearest, request.capacity, + request.limit); + + if (!request.withVertex) { + return manager.serializer(g).writePaths("paths", paths, false); + } + + Set ids = new HashSet<>(); + for (HugeTraverser.Path p : paths) { + ids.addAll(p.vertices()); + } + Iterator iter = QueryResults.emptyIterator(); + if (!ids.isEmpty()) { + iter = g.vertices(ids.toArray()); + } + return manager.serializer(g).writePaths("paths", paths, false, iter); + } + + private static class Request { + + @JsonProperty("sources") + public Vertices sources; + @JsonProperty("targets") + public Vertices targets; + @JsonProperty("step") + public TraverserAPI.Step step; + @JsonProperty("max_depth") + public int depth; + @JsonProperty("nearest") + public boolean nearest = false; + @JsonProperty("capacity") + public long capacity = Long.valueOf(DEFAULT_CAPACITY); + @JsonProperty("limit") + public long limit = Long.valueOf(DEFAULT_PATHS_LIMIT); + @JsonProperty("with_vertex") + public boolean withVertex = false; + + @Override + public String toString() { + return String.format("PathRequest{sources=%s,targets=%s,step=%s," + + "maxDepth=%s,nearest=%s,capacity=%s," + + "limit=%s,withVertex=%s}", this.sources, + this.targets, this.step, this.depth, + this.nearest, this.capacity, + this.limit, this.withVertex); + } + } } diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SameNeighborsAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SameNeighborsAPI.java index ca449c4564..0c6c3c1b83 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SameNeighborsAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SameNeighborsAPI.java @@ -42,7 +42,7 @@ import com.baidu.hugegraph.backend.id.Id; import com.baidu.hugegraph.core.GraphManager; import com.baidu.hugegraph.server.RestServer; -import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.SameNeighborTraverser; import com.baidu.hugegraph.type.define.Directions; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; @@ -75,7 +75,7 @@ public String get(@Context GraphManager manager, Directions dir = Directions.convert(EdgeAPI.parseDirection(direction)); HugeGraph g = graph(manager, graph); - HugeTraverser traverser = new HugeTraverser(g); + SameNeighborTraverser traverser = new SameNeighborTraverser(g); Set neighbors = traverser.sameNeighbors(sourceId, targetId, dir, edgeLabel, degree, limit); return manager.serializer(g).writeList("same_neighbors", neighbors); diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/ShortestPathAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/ShortestPathAPI.java index ab10da79cd..fa97a93e23 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/ShortestPathAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/ShortestPathAPI.java @@ -19,6 +19,8 @@ package com.baidu.hugegraph.api.traversers; +import java.util.List; + import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; @@ -45,6 +47,7 @@ import com.baidu.hugegraph.type.define.Directions; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; +import com.google.common.collect.ImmutableList; @Path("graphs/{graph}/traversers/shortestpath") @Singleton @@ -81,8 +84,11 @@ public String get(@Context GraphManager manager, HugeGraph g = graph(manager, graph); ShortestPathTraverser traverser = new ShortestPathTraverser(g); + + List edgeLabels = edgeLabel == null ? ImmutableList.of() : + ImmutableList.of(edgeLabel); HugeTraverser.Path path = traverser.shortestPath(sourceId, targetId, - dir, edgeLabel, depth, + dir, edgeLabels, depth, degree, skipDegree, capacity); return manager.serializer(g).writeList("path", path.vertices()); diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/TemplatePathsAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/TemplatePathsAPI.java new file mode 100644 index 0000000000..c0e13d8fc0 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/TemplatePathsAPI.java @@ -0,0 +1,160 @@ +/* + * 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.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.inject.Singleton; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; + +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.backend.query.QueryResults; +import com.baidu.hugegraph.core.GraphManager; +import com.baidu.hugegraph.server.RestServer; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.TemplatePathsTraverser; +import com.baidu.hugegraph.util.E; +import com.baidu.hugegraph.util.Log; +import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.annotation.JsonProperty; + +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT; + +@Path("graphs/{graph}/traversers/templatepaths") +@Singleton +public class TemplatePathsAPI extends TraverserAPI { + + private static final Logger LOG = Log.logger(RestServer.class); + + @POST + @Timed + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON_WITH_CHARSET) + public String post(@Context GraphManager manager, + @PathParam("graph") String graph, + Request request) { + E.checkArgumentNotNull(request, "The request body can't be null"); + E.checkArgumentNotNull(request.sources, + "The sources of request can't be null"); + E.checkArgumentNotNull(request.targets, + "The targets of request can't be null"); + E.checkArgument(request.steps != null && !request.steps.isEmpty(), + "The steps of request can't be empty"); + + LOG.debug("Graph [{}] get template paths from source vertices '{}', " + + "target vertices '{}', with steps '{}', " + + "capacity '{}', limit '{}' and with_vertex '{}'", + graph, request.sources, request.targets, request.steps, + request.capacity, request.limit, request.withVertex); + + HugeGraph g = graph(manager, graph); + Iterator sources = request.sources.vertices(g); + Iterator targets = request.targets.vertices(g); + List steps = + steps(g, request.steps); + + TemplatePathsTraverser traverser = new TemplatePathsTraverser(g); + Set paths; + paths = traverser.templatePaths(sources, targets, steps, + request.withRing, request.capacity, + request.limit); + + if (!request.withVertex) { + return manager.serializer(g).writePaths("paths", paths, false); + } + + Set ids = new HashSet<>(); + for (HugeTraverser.Path p : paths) { + ids.addAll(p.vertices()); + } + Iterator iter = QueryResults.emptyIterator(); + if (!ids.isEmpty()) { + iter = g.vertices(ids.toArray()); + } + return manager.serializer(g).writePaths("paths", paths, false, iter); + } + + private static List steps( + HugeGraph g, List steps) { + List edgeSteps = + new ArrayList<>(steps.size()); + for (RepeatEdgeStep step : steps) { + edgeSteps.add(repeatEdgeStep(g, step)); + } + return edgeSteps; + } + + private static class Request { + + @JsonProperty("sources") + public Vertices sources; + @JsonProperty("targets") + public Vertices targets; + @JsonProperty("steps") + public List steps; + @JsonProperty("with_ring") + public boolean withRing = false; + @JsonProperty("capacity") + public long capacity = Long.valueOf(DEFAULT_CAPACITY); + @JsonProperty("limit") + public long limit = Long.valueOf(DEFAULT_PATHS_LIMIT); + @JsonProperty("with_vertex") + public boolean withVertex = false; + + @Override + public String toString() { + return String.format("TemplatePathsRequest{sources=%s,targets=%s," + + "steps=%s,withRing=%s,capacity=%s,limit=%s," + + "withVertex=%s}", + this.sources, this.targets, this.steps, + this.withRing, this.capacity, this.limit, + this.withVertex); + } + } + + protected static class RepeatEdgeStep extends Step { + + @JsonProperty("max_times") + public int maxTimes = 1; + + @Override + public String toString() { + return String.format("RepeatEdgeStep{direction=%s,labels=%s," + + "properties=%s,degree=%s,skipDegree=%s," + + "maxTimes=%s}", + this.direction, this.labels, this.properties, + this.degree, this.skipDegree, this.maxTimes); + } + } +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/TraverserAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/TraverserAPI.java new file mode 100644 index 0000000000..c9af8595da --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/TraverserAPI.java @@ -0,0 +1,72 @@ +/* + * 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 java.util.Map; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.api.API; +import com.baidu.hugegraph.traversal.algorithm.EdgeStep; +import com.baidu.hugegraph.traversal.algorithm.TemplatePathsTraverser; +import com.baidu.hugegraph.type.define.Directions; +import com.fasterxml.jackson.annotation.JsonProperty; + +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; + +public class TraverserAPI extends API { + + protected static EdgeStep step(HugeGraph graph, Step step) { + return new EdgeStep(graph, step.direction, step.labels, step.properties, + step.degree, step.skipDegree); + } + + protected static TemplatePathsTraverser.RepeatEdgeStep repeatEdgeStep( + HugeGraph graph, TemplatePathsAPI.RepeatEdgeStep step) { + return new TemplatePathsTraverser.RepeatEdgeStep(graph, step.direction, + step.labels, + step.properties, + step.degree, + step.skipDegree, + step.maxTimes); + } + + protected static class Step { + + @JsonProperty("direction") + public Directions direction; + @JsonProperty("labels") + public List labels; + @JsonProperty("properties") + public Map properties; + @JsonProperty("degree") + public long degree = Long.valueOf(DEFAULT_DEGREE); + @JsonProperty("skip_degree") + public long skipDegree = 0L; + + @Override + public String toString() { + return String.format("Step{direction=%s,labels=%s,properties=%s," + + "degree=%s,skipDegree=%s}", + this.direction, this.labels, this.properties, + this.degree, this.skipDegree); + } + } +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SourceVertices.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Vertices.java similarity index 97% rename from hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SourceVertices.java rename to hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Vertices.java index 65b24d48b4..4c0a0f5685 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/SourceVertices.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/Vertices.java @@ -37,7 +37,7 @@ import com.baidu.hugegraph.util.E; import com.fasterxml.jackson.annotation.JsonProperty; -public class SourceVertices { +public class Vertices { @JsonProperty("ids") public Set ids; @@ -46,7 +46,7 @@ public class SourceVertices { @JsonProperty("properties") public Map properties; - public Iterator sourcesVertices(HugeGraph g) { + public Iterator vertices(HugeGraph g) { Map props = this.properties; E.checkArgument(!((this.ids == null || this.ids.isEmpty()) && (props == null || props.isEmpty()) && diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/WeightedShortestPathAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/WeightedShortestPathAPI.java index 9935b50876..bcebc05ff6 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/WeightedShortestPathAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/WeightedShortestPathAPI.java @@ -87,7 +87,8 @@ public String get(@Context GraphManager manager, E.checkArgumentNotNull(weight, "The weight property can't be null"); HugeGraph g = graph(manager, graph); - SingleSourceShortestPathTraverser traverser = new SingleSourceShortestPathTraverser(g); + SingleSourceShortestPathTraverser traverser = + new SingleSourceShortestPathTraverser(g); NodeWithWeight path = traverser.weightedShortestPath( sourceId, targetId, dir, edgeLabel, weight, 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 3d19d09f07..e9dc3f2713 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 @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; import org.apache.tinkerpop.gremlin.structure.Edge; @@ -50,6 +51,7 @@ import com.baidu.hugegraph.util.JsonUtil; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; public class JsonSerializer implements Serializer { @@ -286,4 +288,22 @@ public String writeWeightedPaths(WeightedPaths paths, return JsonUtil.toJson(ImmutableMap.of("paths", paths.toMap(), "vertices", vertices)); } + + @Override + public String writeNodesWithPath(String name, Set nodes, + Collection paths, + Iterator iterator, + boolean countOnly) { + List> pathList = new ArrayList<>(); + for (HugeTraverser.Path path : paths) { + pathList.add(path.toMap(false)); + } + + Map results; + results = ImmutableMap.of("size", nodes.size(), + name, countOnly ? ImmutableSet.of() : nodes, + "paths", pathList, + "vertices", iterator); + return JsonUtil.toJson(results); + } } 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 d04849655e..8c680f113b 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,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Vertex; @@ -34,6 +35,7 @@ import com.baidu.hugegraph.schema.PropertyKey; import com.baidu.hugegraph.schema.VertexLabel; import com.baidu.hugegraph.traversal.algorithm.CustomizedCrosspointsTraverser.CrosspointsPaths; + import com.baidu.hugegraph.traversal.algorithm.FusiformSimilarityTraverser.SimilarsMap; import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; import com.baidu.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser.NodeWithWeight; @@ -98,4 +100,9 @@ public String writeWeightedPath(NodeWithWeight path, public String writeWeightedPaths(WeightedPaths paths, Iterator vertices); + + public String writeNodesWithPath(String name, Set nodes, + Collection paths, + Iterator iterator, + boolean countOnly); } 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 1d60f1e73a..1df5905f29 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 @@ -106,10 +106,12 @@ public final class ApiVersion { * [0.55] Issue-994: Support results count for kneighbor/kout/rings * [0.56] Issue-800: Show schema status in schema API * [0.57] Issue-1105: Allow not rebuild index when create index label + * [0.58] Issue-1173: Supports customized kout/kneighbor, + * multi-node-shortest-path, jaccard-similar and template-paths */ // The second parameter of Version.of() is for IDE running without JAR - public static final Version VERSION = Version.of(ApiVersion.class, "0.57"); + public static final Version VERSION = Version.of(ApiVersion.class, "0.58"); 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/HugeFactory.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/HugeFactory.java index d02e3a4e16..2e43f40c1b 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/HugeFactory.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/HugeFactory.java @@ -34,6 +34,7 @@ import com.baidu.hugegraph.config.HugeConfig; import com.baidu.hugegraph.event.EventHub; import com.baidu.hugegraph.task.TaskManager; +import com.baidu.hugegraph.traversal.algorithm.OltpTraverser; import com.baidu.hugegraph.type.define.SerialEnum; import com.baidu.hugegraph.util.E; import com.baidu.hugegraph.util.Log; @@ -127,6 +128,7 @@ public static void shutdown(long timeout) { throw new TimeoutException(timeout + "s"); } TaskManager.instance().shutdown(timeout); + OltpTraverser.destroy(); } catch (Throwable e) { LOG.error("Error while shutdown", e); throw new HugeException("Failed to shutdown", e); diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/config/CoreOptions.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/config/CoreOptions.java index a89fb3c255..38d6321ded 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/config/CoreOptions.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/config/CoreOptions.java @@ -503,4 +503,20 @@ public static synchronized CoreOptions instance() { disallowEmpty(), "./conf/computer.yaml" ); + + public static final ConfigOption OLTP_CONCURRENT_THREADS = + new ConfigOption<>( + "oltp.concurrent_threads", + "Thread number to concurrently execute oltp algorithm.", + rangeInt(0, 65535), + 10 + ); + + public static final ConfigOption OLTP_CONCURRENT_DEPTH = + new ConfigOption<>( + "oltp.concurrent_depth", + "The min depth to enable concurrent oltp algorithm.", + rangeInt(0, 65535), + 10 + ); } diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/CollectionPathsTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/CollectionPathsTraverser.java new file mode 100644 index 0000000000..6f1989464b --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/CollectionPathsTraverser.java @@ -0,0 +1,282 @@ +/* + * 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.traversal.algorithm; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.structure.HugeVertex; +import com.baidu.hugegraph.traversal.algorithm.strategy.TraverseStrategy; +import com.baidu.hugegraph.util.E; +import com.baidu.hugegraph.util.Log; +import com.google.common.collect.ImmutableList; + +public class CollectionPathsTraverser extends HugeTraverser { + + private static final Logger LOG = Log.logger(CollectionPathsTraverser.class); + + public CollectionPathsTraverser(HugeGraph graph) { + super(graph); + } + + public Collection paths(Iterator sources, + Iterator targets, + EdgeStep step, int depth, boolean nearest, + long capacity, long limit) { + checkCapacity(capacity); + checkLimit(limit); + + List sourceList = new ArrayList<>(); + while (sources.hasNext()) { + sourceList.add(((HugeVertex) sources.next()).id()); + } + int sourceSize = sourceList.size(); + E.checkState(sourceSize >= 1 && sourceSize <= MAX_VERTICES, + "The number of source vertices must in [1, %s], " + + "but got: %s", MAX_VERTICES, sourceList.size()); + List targetList = new ArrayList<>(); + while (targets.hasNext()) { + targetList.add(((HugeVertex) targets.next()).id()); + } + int targetSize = targetList.size(); + E.checkState(targetSize >= 1 && targetSize <= MAX_VERTICES, + "The number of target vertices must in [1, %s], " + + "but got: %s", MAX_VERTICES, sourceList.size()); + checkPositive(depth, "max depth"); + + TraverseStrategy strategy = TraverseStrategy.create( + depth >= this.concurrentDepth(), + this.graph()); + Traverser traverser; + if (nearest) { + traverser = new NearestTraverser(this, strategy, + sourceList, targetList, step, + depth, capacity, limit); + } else { + traverser = new Traverser(this, strategy, + sourceList, targetList, step, + depth, capacity, limit); + } + + do { + // Forward + traverser.forward(); + if (traverser.finished()) { + return traverser.paths(); + } + + // Backward + traverser.backward(); + if (traverser.finished()) { + return traverser.paths(); + } + } while (true); + } + + private static class Traverser extends PathTraverser { + + protected final EdgeStep step; + + public Traverser(HugeTraverser traverser, TraverseStrategy strategy, + Collection sources, Collection targets, + EdgeStep step, int depth, long capacity, long limit) { + super(traverser, strategy, sources, targets, capacity, limit); + this.step = step; + this.totalSteps = depth; + } + + @Override + public EdgeStep nextStep(boolean forward) { + return this.step; + } + + @Override + protected void processOneForForward(Id sourceV, Id targetV) { + for (Node source : this.sources.get(sourceV)) { + // If have loop, skip target + if (source.contains(targetV)) { + continue; + } + + // If cross point exists, path found, concat them + if (this.targetsAll.containsKey(targetV)) { + for (Node target : this.targetsAll.get(targetV)) { + List path = source.joinPath(target); + if (!path.isEmpty()) { + this.paths.add(new Path(targetV, path)); + if (this.reachLimit()) { + return; + } + } + } + } + + // Add node to next start-nodes + this.addNodeToNewVertices(targetV, new Node(targetV, source)); + } + } + + @Override + protected void processOneForBackward(Id sourceV, Id targetV) { + for (Node source : this.targets.get(sourceV)) { + // If have loop, skip target + if (source.contains(targetV)) { + continue; + } + + // If cross point exists, path found, concat them + if (this.sourcesAll.containsKey(targetV)) { + for (Node target : this.sourcesAll.get(targetV)) { + List path = source.joinPath(target); + if (!path.isEmpty()) { + Path newPath = new Path(targetV, path); + newPath.reverse(); + this.paths.add(newPath); + if (this.reachLimit()) { + return; + } + } + } + } + + // Add node to next start-nodes + this.addNodeToNewVertices(targetV, new Node(targetV, source)); + } + } + + @Override + protected void reInitCurrentStepIfNeeded(EdgeStep step, + boolean forward) { + if (forward) { + // Re-init sources + this.sources = this.newVertices; + // Record all passed vertices + this.addNewVerticesToAll(this.sourcesAll); + } else { + // Re-init targets + this.targets = this.newVertices; + // Record all passed vertices + this.addNewVerticesToAll(this.targetsAll); + } + } + } + + private class NearestTraverser extends Traverser { + + public NearestTraverser(HugeTraverser traverser, + TraverseStrategy strategy, + Collection sources, Collection targets, + EdgeStep step, int depth, long capacity, + long limit) { + super(traverser, strategy, sources, targets, step, + depth, capacity, limit); + } + + @Override + protected void processOneForForward(Id sourceV, Id targetV) { + Node source = this.sources.get(sourceV).get(0); + // If have loop, skip target + if (source.contains(targetV)) { + return; + } + + // If cross point exists, path found, concat them + if (this.targetsAll.containsKey(targetV)) { + Node node = this.targetsAll.get(targetV).get(0); + List path = source.joinPath(node); + if (!path.isEmpty()) { + this.paths.add(new Path(targetV, path)); + if (this.reachLimit()) { + return; + } + } + } + + // Add node to next start-nodes + this.addNodeToNewVertices(targetV, new Node(targetV, source)); + } + + @Override + protected void processOneForBackward(Id sourceV, Id targetV) { + Node sourcee = this.targets.get(sourceV).get(0); + // If have loop, skip target + if (sourcee.contains(targetV)) { + return; + } + + // If cross point exists, path found, concat them + if (this.sourcesAll.containsKey(targetV)) { + Node node = this.sourcesAll.get(targetV).get(0); + List path = sourcee.joinPath(node); + if (!path.isEmpty()) { + Path newPath = new Path(targetV, path); + newPath.reverse(); + this.paths.add(newPath); + if (this.reachLimit()) { + return; + } + } + } + + // Add node to next start-nodes + this.addNodeToNewVertices(targetV, new Node(targetV, sourcee)); + } + + @Override + protected void reInitCurrentStepIfNeeded(EdgeStep step, + boolean forward) { + if (forward) { + // Re-init targets + this.sources = this.newVertices; + // Record all passed vertices + this.addNewVerticesToAll(this.sourcesAll); + } else { + // Re-init targets + this.targets = this.newVertices; + // Record all passed vertices + this.addNewVerticesToAll(this.targetsAll); + } + } + + @Override + public void addNodeToNewVertices(Id id, Node node) { + this.newVertices.putIfAbsent(id, ImmutableList.of(node)); + } + + @Override + public void addNewVerticesToAll(Map> targets) { + for (Map.Entry> entry : this.newVertices.entrySet()) { + targets.putIfAbsent(entry.getKey(), entry.getValue()); + } + } + + protected int accessedNodes() { + return this.sourcesAll.size() + this.targetsAll.size(); + } + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/EdgeStep.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/EdgeStep.java index f9a43b6d88..f27e435b84 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/EdgeStep.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/EdgeStep.java @@ -37,7 +37,7 @@ public class EdgeStep { - protected final Directions direction; + protected Directions direction; protected final Map labels; protected final Map properties; protected final long degree; @@ -80,6 +80,10 @@ public Id[] edgeLabels() { return edgeLabels; } + public void swithDirection() { + this.direction = this.direction.opposite(); + } + public long limit() { long limit = this.skipDegree > 0L ? this.skipDegree : this.degree; return limit; diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/FusiformSimilarityTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/FusiformSimilarityTraverser.java index f07542732a..17a534ab0e 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/FusiformSimilarityTraverser.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/FusiformSimilarityTraverser.java @@ -194,7 +194,13 @@ private Set fusiformSimilarityForVertex( } private static void checkGroupArgs(String groupProperty, int minGroups) { - if (groupProperty != null) { + if (groupProperty == null) { + E.checkArgument(minGroups == 0, + "Can't set min group count when " + + "group property not set"); + } else { + E.checkArgument(!groupProperty.isEmpty(), + "The group property can't be empty"); E.checkArgument(minGroups > 0, "Must set min group count when " + "group property set"); diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java index 0cc21abf50..8f6102f9ae 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java @@ -28,13 +28,13 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import org.apache.commons.collections.CollectionUtils; import org.apache.tinkerpop.gremlin.structure.Edge; -import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; import com.baidu.hugegraph.HugeException; import com.baidu.hugegraph.HugeGraph; @@ -44,8 +44,11 @@ import com.baidu.hugegraph.backend.query.Query; import com.baidu.hugegraph.backend.query.QueryResults; import com.baidu.hugegraph.backend.tx.GraphTransaction; +import com.baidu.hugegraph.config.CoreOptions; +import com.baidu.hugegraph.config.HugeConfig; import com.baidu.hugegraph.exception.NotFoundException; import com.baidu.hugegraph.iterator.ExtendableIterator; +import com.baidu.hugegraph.iterator.FilterIterator; import com.baidu.hugegraph.iterator.MapperIterator; import com.baidu.hugegraph.schema.SchemaLabel; import com.baidu.hugegraph.structure.HugeEdge; @@ -64,8 +67,6 @@ public class HugeTraverser { private HugeGraph graph; - public static final List PATH_NONE = ImmutableList.of(); - public static final String DEFAULT_CAPACITY = "10000000"; public static final String DEFAULT_ELEMENTS_LIMIT = "10000000"; public static final String DEFAULT_PATHS_LIMIT = "10"; @@ -76,6 +77,8 @@ public class HugeTraverser { public static final String DEFAULT_MAX_DEPTH = "50"; public static final String DEFAULT_WEIGHT = "0"; + protected static final int MAX_VERTICES = 10; + // Empirical value of scan limit, with which results can be returned in 3s public static final String DEFAULT_PAGE_LIMIT = "100000"; @@ -89,144 +92,17 @@ public HugeGraph graph() { return this.graph; } - public Set kout(Id sourceV, Directions dir, String label, - int depth, boolean nearest, - long degree, long capacity, long limit) { - E.checkNotNull(sourceV, "source vertex id"); - this.checkVertexExist(sourceV, "source vertex"); - E.checkNotNull(dir, "direction"); - checkPositive(depth, "k-out max_depth"); - checkDegree(degree); - checkCapacity(capacity); - checkLimit(limit); - if (capacity != NO_LIMIT) { - // Capacity must > limit because sourceV is counted in capacity - E.checkArgument(capacity >= limit && limit != NO_LIMIT, - "Capacity can't be less than limit, " + - "but got capacity '%s' and limit '%s'", - capacity, limit); - } - - Id labelId = this.getEdgeLabelId(label); - - Set latest = newSet(); - latest.add(sourceV); - - Set all = newSet(); - all.add(sourceV); - - long remaining = capacity == NO_LIMIT ? - NO_LIMIT : capacity - latest.size(); - while (depth-- > 0) { - // Just get limit nodes in last layer if limit < remaining capacity - if (depth == 0 && limit != NO_LIMIT && - (limit < remaining || remaining == NO_LIMIT)) { - remaining = limit; - } - if (nearest) { - latest = this.adjacentVertices(latest, dir, labelId, all, - degree, remaining); - all.addAll(latest); - } else { - latest = this.adjacentVertices(latest, dir, labelId, null, - degree, remaining); - } - if (capacity != NO_LIMIT) { - // Update 'remaining' value to record remaining capacity - remaining -= latest.size(); - - if (remaining <= 0 && depth > 0) { - throw new HugeException( - "Reach capacity '%s' while remaining depth '%s'", - capacity, depth); - } - } - } - - return latest; - } - - public Set kneighbor(Id sourceV, Directions dir, - String label, int depth, - long degree, long limit) { - E.checkNotNull(sourceV, "source vertex id"); - this.checkVertexExist(sourceV, "source vertex"); - E.checkNotNull(dir, "direction"); - checkPositive(depth, "k-neighbor max_depth"); - checkDegree(degree); - checkLimit(limit); - - Id labelId = this.getEdgeLabelId(label); - - Set latest = newSet(); - latest.add(sourceV); - - Set all = newSet(); - all.add(sourceV); - - while (depth-- > 0) { - long remaining = limit == NO_LIMIT ? NO_LIMIT : limit - all.size(); - latest = this.adjacentVertices(latest, dir, labelId, all, - degree, remaining); - all.addAll(latest); - if (limit != NO_LIMIT && all.size() >= limit) { - break; - } - } - - return all; - } - - public Set sameNeighbors(Id vertex, Id other, Directions direction, - String label, long degree, long limit) { - E.checkNotNull(vertex, "vertex id"); - E.checkNotNull(other, "the other vertex id"); - this.checkVertexExist(vertex, "vertex"); - this.checkVertexExist(other, "other vertex"); - E.checkNotNull(direction, "direction"); - checkDegree(degree); - checkLimit(limit); - - Id labelId = this.getEdgeLabelId(label); - - Set sourceNeighbors = IteratorUtils.set(this.adjacentVertices( - vertex, direction, labelId, degree)); - Set targetNeighbors = IteratorUtils.set(this.adjacentVertices( - other, direction, labelId, degree)); - Set sameNeighbors = (Set) CollectionUtil.intersect( - sourceNeighbors, targetNeighbors); - if (limit != NO_LIMIT) { - int end = Math.min(sameNeighbors.size(), (int) limit); - sameNeighbors = CollectionUtil.subSet(sameNeighbors, 0, end); - } - return sameNeighbors; + protected int concurrentDepth() { + return this.config().get(CoreOptions.OLTP_CONCURRENT_DEPTH); } - public double jaccardSimilarity(Id vertex, Id other, Directions dir, - String label, long degree) { - E.checkNotNull(vertex, "vertex id"); - E.checkNotNull(other, "the other vertex id"); - this.checkVertexExist(vertex, "vertex"); - this.checkVertexExist(other, "other vertex"); - E.checkNotNull(dir, "direction"); - checkDegree(degree); - - Id labelId = this.getEdgeLabelId(label); - - Set sourceNeighbors = IteratorUtils.set(this.adjacentVertices( - vertex, dir, labelId, degree)); - Set targetNeighbors = IteratorUtils.set(this.adjacentVertices( - other, dir, labelId, degree)); - int interNum = CollectionUtil.intersect(sourceNeighbors, - targetNeighbors).size(); - int unionNum = CollectionUtil.union(sourceNeighbors, - targetNeighbors).size(); - return (double) interNum / unionNum; + protected HugeConfig config() { + return ((HugeConfig) this.graph().hugegraph().configuration()); } - private Set adjacentVertices(Set vertices, Directions dir, - Id label, Set excluded, - long degree, long limit) { + protected Set adjacentVertices(Set vertices, Directions dir, + Id label, Set excluded, + long degree, long limit) { if (limit == 0) { return ImmutableSet.of(); } @@ -259,6 +135,35 @@ protected Iterator adjacentVertices(Id source, Directions dir, }); } + protected Set adjacentVertices(Id source, EdgeStep step) { + Set neighbors = new HashSet<>(); + Iterator edges = this.edgesOfVertex(source, step); + while (edges.hasNext()) { + neighbors.add(((HugeEdge) edges.next()).id().otherVertexId()); + } + return neighbors; + } + + protected Set adjacentVertices(Set vertices, EdgeStep step, + Set excluded, long remaining) { + Set neighbors = newSet(); + for (Node source : vertices) { + Iterator edges = this.edgesOfVertex(source.id(), step); + while (edges.hasNext()) { + Id target = ((HugeEdge) edges.next()).id().otherVertexId(); + KNode kNode = new KNode(target, (KNode) source); + if (excluded != null && excluded.contains(kNode)) { + continue; + } + neighbors.add(kNode); + if (--remaining <= 0L) { + return neighbors; + } + } + } + return neighbors; + } + protected Iterator edgesOfVertex(Id source, Directions dir, Id label, long limit) { Id[] labels = {}; @@ -309,16 +214,24 @@ private Iterator edgesOfVertex(Id source, EdgeStep edgeStep, Query query = GraphTransaction.constructEdgesQuery(source, edgeStep.direction, edgeLabels); + ConditionQuery filter = null; if (mustAllSK) { this.fillFilterBySortKeys(query, edgeLabels, edgeStep.properties); } else { - this.fillFilterByProperties(query, edgeStep.properties); + filter = (ConditionQuery) query.copy(); + this.fillFilterByProperties(filter, edgeStep.properties); } query.capacity(Query.NO_CAPACITY); if (edgeStep.limit() != NO_LIMIT) { query.limit(edgeStep.limit()); } Iterator edges = this.graph().edges(query); + if (filter != null) { + ConditionQuery finalFilter = filter; + edges = new FilterIterator<>(edges, (e) -> { + return finalFilter.test((HugeEdge) e); + }); + } return edgeStep.skipSuperNodeIfNeeded(edges); } @@ -500,7 +413,15 @@ public static Iterator skipSuperNodeIfNeeded(Iterator edges, } protected static Set newSet() { - return new HashSet<>(); + return newSet(false); + } + + protected static Set newSet(boolean concurrent) { + if (concurrent) { + return ConcurrentHashMap.newKeySet(); + } else { + return new HashSet<>(); + } } protected static Map newMap() { @@ -511,6 +432,26 @@ protected static MultivaluedMap newMultivalueMap() { return new MultivaluedHashMap<>(); } + protected static List joinPath(Node prev, Node back, boolean ring) { + // Get self path + List path = prev.path(); + + // Get reversed other path + List backPath = back.path(); + Collections.reverse(backPath); + + if (!ring) { + // Avoid loop in path + if (CollectionUtils.containsAny(path, backPath)) { + return ImmutableList.of(); + } + } + + // Append other path behind self path + path.addAll(backPath); + return path; + } + public static class Node { private Id id; @@ -546,21 +487,7 @@ public List path() { } public List joinPath(Node back) { - // Get self path - List path = this.path(); - - // Get reversed other path - List backPath = back.path(); - Collections.reverse(backPath); - - // Avoid loop in path - if (CollectionUtils.containsAny(path, backPath)) { - return ImmutableList.of(); - } - - // Append other path behind self path - path.addAll(backPath); - return path; + return HugeTraverser.joinPath(this, back, false); } public boolean contains(Id id) { @@ -590,6 +517,22 @@ public boolean equals(Object object) { } } + public static class KNode extends Node { + + public KNode(Id id, KNode parent) { + super(id, parent); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof KNode)) { + return false; + } + KNode other = (KNode) object; + return Objects.equals(this.id(), other.id()); + } + } + public static class Path { public static final Path EMPTY_PATH = new Path(ImmutableList.of()); diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/JaccardSimilarTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/JaccardSimilarTraverser.java new file mode 100644 index 0000000000..82e679c250 --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/JaccardSimilarTraverser.java @@ -0,0 +1,211 @@ +/* + * 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.traversal.algorithm; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; + +import com.baidu.hugegraph.HugeException; +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.CollectionUtil; +import com.baidu.hugegraph.util.E; +import com.google.common.collect.ImmutableMap; + +public class JaccardSimilarTraverser extends OltpTraverser { + + public JaccardSimilarTraverser(HugeGraph graph) { + super(graph); + } + + + public double jaccardSimilarity(Id vertex, Id other, Directions dir, + String label, long degree) { + E.checkNotNull(vertex, "vertex id"); + E.checkNotNull(other, "the other vertex id"); + this.checkVertexExist(vertex, "vertex"); + this.checkVertexExist(other, "other vertex"); + E.checkNotNull(dir, "direction"); + checkDegree(degree); + + Id labelId = this.getEdgeLabelId(label); + + Set sourceNeighbors = IteratorUtils.set(this.adjacentVertices( + vertex, dir, labelId, degree)); + Set targetNeighbors = IteratorUtils.set(this.adjacentVertices( + other, dir, labelId, degree)); + return jaccardSimilarity(sourceNeighbors, targetNeighbors); + } + + public double jaccardSimilarity(Set set1, Set set2) { + int interNum = CollectionUtil.intersect(set1, set2).size(); + int unionNum = CollectionUtil.union(set1, set2).size(); + return (double) interNum / unionNum; + } + + public Map jaccardSimilars(Id source, EdgeStep step, + int top, long capacity) { + E.checkNotNull(source, "source vertex id"); + this.checkVertexExist(source, "source vertex"); + checkCapacity(capacity); + + Map results; + if (3 >= this.concurrentDepth() && + step.direction == Directions.BOTH) { + results = this.jaccardSimilarsConcurrent(source, step, capacity); + } else { + results = this.jaccardSimilarsSingle(source, step, capacity); + } + + if (top > 0) { + results = HugeTraverser.topN(results, true, top); + } + + return results; + } + + public Map jaccardSimilarsConcurrent(Id source, EdgeStep step, + long capacity) { + AtomicLong count = new AtomicLong(0L); + Set accessed = ConcurrentHashMap.newKeySet(); + accessed.add(source); + reachCapacity(count.incrementAndGet(), capacity); + + // Query neighbors + Set layer1s = this.adjacentVertices(source, step); + reachCapacity(count.get() + layer1s.size(), capacity); + count.addAndGet(layer1s.size()); + if (layer1s.isEmpty()) { + return ImmutableMap.of(); + } + + Map results = new ConcurrentHashMap<>(); + Set layer2All = ConcurrentHashMap.newKeySet(); + + this.traverseIds(layer1s.iterator(), id -> { + // Skip if accessed already + if (accessed.contains(id)) { + return; + } + Set layer2s = this.adjacentVertices(id, step); + if (layer2s.isEmpty()) { + results.put(id, 0.0D); + } + + layer2All.addAll(layer2s); + reachCapacity(count.get() + layer2All.size(), capacity); + double jaccardSimilarity = this.jaccardSimilarity(layer1s, layer2s); + results.put(id, jaccardSimilarity); + accessed.add(id); + }); + + count.addAndGet(layer2All.size()); + + this.traverseIds(layer2All.iterator(), id -> { + // Skip if accessed already + if (accessed.contains(id)) { + return; + } + Set layer3s = this.adjacentVertices(id, step); + reachCapacity(count.get() + layer3s.size(), capacity); + if (layer3s.isEmpty()) { + results.put(id, 0.0D); + } + + double jaccardSimilarity = this.jaccardSimilarity(layer1s, layer3s); + results.put(id, jaccardSimilarity); + accessed.add(id); + }); + + return results; + } + + public Map jaccardSimilarsSingle(Id source, EdgeStep step, + long capacity) { + long count = 0L; + Set accessed = new HashSet<>(); + accessed.add(source); + reachCapacity(++count, capacity); + + // Query neighbors + Set layer1s = this.adjacentVertices(source, step); + reachCapacity(count + layer1s.size(), capacity); + count += layer1s.size(); + if (layer1s.isEmpty()) { + return ImmutableMap.of(); + } + + Map results = new HashMap<>(); + Set layer2s; + Set layer2All = new HashSet<>(); + double jaccardSimilarity; + for (Id neighbor : layer1s) { + // Skip if accessed already + if (accessed.contains(neighbor)) { + continue; + } + layer2s = this.adjacentVertices(neighbor, step); + if (layer2s.isEmpty()) { + results.put(neighbor, 0.0D); + continue; + } + + layer2All.addAll(layer2s); + reachCapacity(count + layer2All.size(), capacity); + jaccardSimilarity = this.jaccardSimilarity(layer1s, layer2s); + results.put(neighbor, jaccardSimilarity); + accessed.add(neighbor); + } + count += layer2All.size(); + + Set layer3s; + for (Id neighbor : layer2All) { + // Skip if accessed already + if (accessed.contains(neighbor)) { + continue; + } + layer3s = this.adjacentVertices(neighbor, step); + reachCapacity(count + layer3s.size(), capacity); + if (layer3s.isEmpty()) { + results.put(neighbor, 0.0D); + continue; + } + + jaccardSimilarity = this.jaccardSimilarity(layer1s, layer3s); + results.put(neighbor, jaccardSimilarity); + accessed.add(neighbor); + } + + return results; + } + + private static void reachCapacity(long count, long capacity) { + if (capacity != NO_LIMIT && count > capacity) { + throw new HugeException("Reach capacity '%s'", capacity); + } + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/KneighborTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/KneighborTraverser.java new file mode 100644 index 0000000000..3c68021b5e --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/KneighborTraverser.java @@ -0,0 +1,109 @@ +/* + * 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.traversal.algorithm; + +import java.util.Iterator; +import java.util.Set; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.E; + +public class KneighborTraverser extends OltpTraverser { + + public KneighborTraverser(HugeGraph graph) { + super(graph); + } + + public Set kneighbor(Id sourceV, Directions dir, + String label, int depth, + long degree, long limit) { + E.checkNotNull(sourceV, "source vertex id"); + this.checkVertexExist(sourceV, "source vertex"); + E.checkNotNull(dir, "direction"); + checkPositive(depth, "k-neighbor max_depth"); + checkDegree(degree); + checkLimit(limit); + + Id labelId = this.getEdgeLabelId(label); + + Set latest = newSet(); + latest.add(sourceV); + + Set all = newSet(); + all.add(sourceV); + + while (depth-- > 0) { + long remaining = limit == NO_LIMIT ? NO_LIMIT : limit - all.size(); + latest = this.adjacentVertices(latest, dir, labelId, all, + degree, remaining); + all.addAll(latest); + if (limit != NO_LIMIT && all.size() >= limit) { + break; + } + } + + return all; + } + + public Set customizedKneighbor(Id source, EdgeStep step, + int maxDepth, long limit) { + E.checkNotNull(source, "source vertex id"); + this.checkVertexExist(source, "source vertex"); + checkPositive(maxDepth, "k-neighbor max_depth"); + checkLimit(limit); + + boolean single = maxDepth < this.concurrentDepth() || + step.direction != Directions.BOTH; + return this.customizedKneighbor(source, step, maxDepth, + limit, single); + } + + public Set customizedKneighbor(Id source, EdgeStep step, int maxDepth, + long limit, boolean single) { + Set latest = newSet(single); + Set all = newSet(single); + + Node sourceV = new KNode(source, null); + + latest.add(sourceV); + all.add(sourceV); + + while (maxDepth-- > 0) { + long remaining = limit == NO_LIMIT ? NO_LIMIT : limit - all.size(); + latest = this.adjacentVertices(latest, step, all, + remaining, single); + int size = all.size() + latest.size(); + if (limit != NO_LIMIT && size >= limit) { + int subLength = (int) limit - all.size(); + Iterator iterator = latest.iterator(); + for (int i = 0; i < subLength && iterator.hasNext(); i++) { + all.add(iterator.next()); + } + break; + } else { + all.addAll(latest); + } + } + + return all; + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/KoutTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/KoutTraverser.java new file mode 100644 index 0000000000..fcfddf6bbc --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/KoutTraverser.java @@ -0,0 +1,157 @@ +/* + * 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.traversal.algorithm; + +import java.util.Set; + +import com.baidu.hugegraph.HugeException; +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.CollectionUtil; +import com.baidu.hugegraph.util.E; + +public class KoutTraverser extends OltpTraverser { + + public KoutTraverser(HugeGraph graph) { + super(graph); + } + + public Set kout(Id sourceV, Directions dir, String label, + int depth, boolean nearest, + long degree, long capacity, long limit) { + E.checkNotNull(sourceV, "source vertex id"); + this.checkVertexExist(sourceV, "source vertex"); + E.checkNotNull(dir, "direction"); + checkPositive(depth, "k-out max_depth"); + checkDegree(degree); + checkCapacity(capacity); + checkLimit(limit); + if (capacity != NO_LIMIT) { + // Capacity must > limit because sourceV is counted in capacity + E.checkArgument(capacity >= limit && limit != NO_LIMIT, + "Capacity can't be less than limit, " + + "but got capacity '%s' and limit '%s'", + capacity, limit); + } + + Id labelId = this.getEdgeLabelId(label); + + Set latest = newSet(); + latest.add(sourceV); + + Set all = newSet(); + all.add(sourceV); + + long remaining = capacity == NO_LIMIT ? + NO_LIMIT : capacity - latest.size(); + while (depth-- > 0) { + // Just get limit nodes in last layer if limit < remaining capacity + if (depth == 0 && limit != NO_LIMIT && + (limit < remaining || remaining == NO_LIMIT)) { + remaining = limit; + } + if (nearest) { + latest = this.adjacentVertices(latest, dir, labelId, all, + degree, remaining); + all.addAll(latest); + } else { + latest = this.adjacentVertices(latest, dir, labelId, null, + degree, remaining); + } + if (capacity != NO_LIMIT) { + // Update 'remaining' value to record remaining capacity + remaining -= latest.size(); + + if (remaining <= 0 && depth > 0) { + throw new HugeException( + "Reach capacity '%s' while remaining depth '%s'", + capacity, depth); + } + } + } + + return latest; + } + + public Set customizedKout(Id source, EdgeStep step, int maxDepth, + boolean nearest, long capacity, + long limit) { + E.checkNotNull(source, "source vertex id"); + this.checkVertexExist(source, "source vertex"); + checkPositive(maxDepth, "k-out max_depth"); + checkCapacity(capacity); + checkLimit(limit); + + Set results; + boolean single = maxDepth < this.concurrentDepth() || + step.direction != Directions.BOTH; + results = this.customizedKout(source, step, maxDepth, nearest, + capacity, single); + + if (limit != NO_LIMIT && results.size() > limit) { + results = CollectionUtil.subSet(results, 0, (int) limit); + } + + return results; + } + + public Set customizedKout(Id source, EdgeStep step, int maxDepth, + boolean nearest, long capacity, + boolean single) { + Set latest = newSet(single); + Set all = newSet(single); + + Node sourceV = new KNode(source, null); + + latest.add(sourceV); + all.add(sourceV); + + int depth = maxDepth; + long remaining = capacity == NO_LIMIT ? + NO_LIMIT : capacity - latest.size(); + while (depth-- > 0) { + if (nearest) { + latest = this.adjacentVertices(latest, step, all, + remaining, single); + all.addAll(latest); + } else { + latest = this.adjacentVertices(latest, step, null, + remaining, single); + } + if (capacity != NO_LIMIT) { + // Update 'remaining' value to record remaining capacity + remaining -= latest.size(); + reachCapacity(remaining, capacity, depth); + } + } + + return latest; + } + + private static void reachCapacity(long remaining, long capacity, + int depth) { + if (remaining <= 0 && depth > 0) { + throw new HugeException( + "Reach capacity '%s' while remaining depth '%s'", + capacity, depth); + } + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/MultiNodeShortestPathTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/MultiNodeShortestPathTraverser.java new file mode 100644 index 0000000000..633b5bd79f --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/MultiNodeShortestPathTraverser.java @@ -0,0 +1,132 @@ +/* + * 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.traversal.algorithm; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.structure.HugeVertex; +import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.E; + +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.Path.EMPTY_PATH; + +public class MultiNodeShortestPathTraverser extends OltpTraverser { + + public MultiNodeShortestPathTraverser(HugeGraph graph) { + super(graph); + } + + public List multiNodeShortestPath(Iterator vertices, + EdgeStep step, int maxDepth, + long capacity) { + List vertexList = IteratorUtils.list(vertices); + int vertexCount = vertexList.size(); + E.checkState(vertexCount >= 2 && vertexCount <= MAX_VERTICES, + "The number of vertices of multiple node shortest path " + + "must in [2, %s], but got: %s", + MAX_VERTICES, vertexList.size()); + List> pairs = new ArrayList<>(); + cmn(vertexList, vertexCount, 2, 0, null, r -> { + Id source = ((HugeVertex) r.get(0)).id(); + Id target = ((HugeVertex) r.get(1)).id(); + Pair pair = Pair.of(source, target); + pairs.add(pair); + }); + + if (maxDepth >= this.concurrentDepth() && + step.direction == Directions.BOTH || + vertexCount > 10) { + return this.multiNodeShortestPathConcurrent(pairs, step, + maxDepth, capacity); + } else { + return this.multiNodeShortestPathSingle(pairs, step, + maxDepth, capacity); + } + } + + public List multiNodeShortestPathConcurrent(List> pairs, + EdgeStep step, + int maxDepth, + long capacity) { + List results = new CopyOnWriteArrayList<>(); + ShortestPathTraverser traverser = + new ShortestPathTraverser(this.graph()); + this.traversePairs(pairs.iterator(), pair -> { + Path path = traverser.shortestPath(pair.getLeft(), pair.getRight(), + step, maxDepth, capacity); + if (!EMPTY_PATH.equals(path)) { + results.add(path); + } + }); + + return results; + } + + public List multiNodeShortestPathSingle(List> pairs, + EdgeStep step, int maxDepth, + long capacity) { + List results = new ArrayList<>(); + ShortestPathTraverser traverser = + new ShortestPathTraverser(this.graph()); + for (Pair pair : pairs) { + Path path = traverser.shortestPath(pair.getLeft(), pair.getRight(), + step, maxDepth, capacity); + if (!EMPTY_PATH.equals(path)) { + results.add(path); + } + } + return results; + } + + private static void cmn(List all, int m, int n, int current, + List result, Consumer> consumer) { + assert m <= all.size(); + assert current < all.size(); + if (result == null) { + result = new ArrayList<>(n); + } + if (n == 0) { + // All n items are selected + consumer.accept(result); + return; + } + if (m < n || current >= all.size()) { + return; + } + + // Select current item, continue to select C(m-1, n-1) + int index = result.size(); + result.add(all.get(current)); + cmn(all, m - 1, n - 1, ++current, result, consumer); + // Not select current item, continue to select C(m-1, n) + result.remove(index); + cmn(all, m - 1, n, current, result, consumer); + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/OltpTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/OltpTraverser.java new file mode 100644 index 0000000000..b35c1713d2 --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/OltpTraverser.java @@ -0,0 +1,198 @@ +/* + * 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.traversal.algorithm; + +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Element; +import org.apache.tinkerpop.gremlin.structure.Property; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.config.CoreOptions; +import com.baidu.hugegraph.iterator.FilterIterator; +import com.baidu.hugegraph.structure.HugeEdge; +import com.baidu.hugegraph.util.Consumers; + +import jersey.repackaged.com.google.common.base.Objects; + +public abstract class OltpTraverser extends HugeTraverser + implements AutoCloseable { + + private static final String EXECUTOR_NAME = "oltp"; + private static ExecutorService executor; + + protected OltpTraverser(HugeGraph graph) { + super(graph); + if (executor != null) { + return; + } + synchronized (OltpTraverser.class) { + if (executor != null) { + return; + } + int workers = this.config().get(CoreOptions.OLTP_CONCURRENT_THREADS); + if (workers > 0) { + executor = Consumers.newThreadPool(EXECUTOR_NAME, workers); + } + } + } + + @Override + public void close() { + // pass + } + + public static void destroy() { + if (executor != null) { + executor.shutdown(); + executor = null; + } + } + + protected Set adjacentVertices(Set latest, EdgeStep step, + Set all, long remaining, + boolean single) { + if (single) { + return this.adjacentVertices(latest, step, all, remaining); + } else { + AtomicLong remain = new AtomicLong(remaining); + return this.adjacentVertices(latest, step, all, remain); + } + } + + protected Set adjacentVertices(Set vertices, EdgeStep step, + Set excluded, + AtomicLong remaining) { + Set neighbors = ConcurrentHashMap.newKeySet(); + this.traverseNodes(vertices.iterator(), v -> { + Iterator edges = this.edgesOfVertex(v.id(), step); + while (edges.hasNext()) { + Id target = ((HugeEdge) edges.next()).id().otherVertexId(); + KNode kNode = new KNode(target, (KNode) v); + if (excluded != null && excluded.contains(kNode)) { + continue; + } + neighbors.add(kNode); + if (remaining.decrementAndGet() <= 0L) { + return; + } + } + }); + return neighbors; + } + + protected long traverseNodes(Iterator vertices, + Consumer consumer) { + return this.traverse(vertices, consumer, "traverse-nodes"); + } + + protected long traversePairs(Iterator> pairs, + Consumer> consumer) { + return this.traverse(pairs, consumer, "traverse-pairs"); + } + + protected long traverseIds(Iterator ids, Consumer consumer) { + return this.traverse(ids, consumer, "traverse-ids"); + } + + protected long traverse(Iterator iterator, Consumer consumer, + String name) { + Consumers consumers = new Consumers<>(executor, consumer, null); + consumers.start(name); + long total = 0L; + try { + while (iterator.hasNext()) { + total++; + K v = iterator.next(); + consumers.provide(v); + } + } catch (Consumers.StopExecution e) { + // pass + } catch (Throwable e) { + throw Consumers.wrapException(e); + } finally { + try { + consumers.await(); + } catch (Throwable e) { + Consumers.wrapException(e); + } finally { + CloseableIterator.closeIterator(iterator); + } + } + return total; + } + + protected Iterator filter(Iterator vertices, + String key, Object value) { + return new FilterIterator<>(vertices, vertex -> { + return match(vertex, key, value); + }); + } + + protected boolean match(Element elem, String key, Object value) { + // check property key exists + this.graph().propertyKey(key); + // return true if property value exists & equals to specified value + Property p = elem.property(key); + return p.isPresent() && Objects.equal(p.value(), value); + } + + public class ConcurrentMultiValuedMap + extends ConcurrentHashMap> { + + public ConcurrentMultiValuedMap() { + super(); + } + + public void add(K key, V value) { + List values = this.getValues(key); + values.add(value); + } + + public void addAll(K key, List value) { + List values = this.getValues(key); + values.addAll(value); + } + + public List getValues(K key) { + List values = this.get(key); + if (values == null) { + values = new CopyOnWriteArrayList<>(); + List old = this.putIfAbsent(key, values); + if (old != null) { + values = old; + } + } + return values; + } + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/PathTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/PathTraverser.java new file mode 100644 index 0000000000..9551009e5e --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/PathTraverser.java @@ -0,0 +1,225 @@ +/* + * 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.traversal.algorithm; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; + +import org.apache.tinkerpop.gremlin.structure.Edge; + +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.structure.HugeEdge; +import com.baidu.hugegraph.traversal.algorithm.strategy.TraverseStrategy; + +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.NO_LIMIT; + +public abstract class PathTraverser { + + protected final HugeTraverser traverser; + + protected int stepCount; + protected final long capacity; + protected final long limit; + protected int totalSteps; // TODO: delete or implement abstract method + + protected Map> sources; + protected Map> sourcesAll; + protected Map> targets; + protected Map> targetsAll; + + protected Map> newVertices; + + protected Set paths; + + protected TraverseStrategy traverseStrategy; + + public PathTraverser(HugeTraverser traverser, TraverseStrategy strategy, + Collection sources, Collection targets, + long capacity, long limit) { + this.traverser = traverser; + this.traverseStrategy = strategy; + + this.capacity = capacity; + this.limit = limit; + + this.stepCount = 0; + + this.sources = this.newMultiValueMap(); + this.sourcesAll = this.newMultiValueMap(); + this.targets = this.newMultiValueMap(); + this.targetsAll = this.newMultiValueMap(); + + for (Id id : sources) { + this.addNode(this.sources, id, new HugeTraverser.Node(id)); + } + for (Id id : targets) { + this.addNode(this.targets, id, new HugeTraverser.Node(id)); + } + this.sourcesAll.putAll(this.sources); + this.targetsAll.putAll(this.targets); + + this.paths = this.newPathSet(); + } + + public void forward() { + EdgeStep currentStep = this.nextStep(true); + if (currentStep == null) { + return; + } + + this.beforeTraverse(true); + + // Traversal vertices of previous level + this.traverseOneLayer(this.sources, currentStep, this::forward); + + this.afterTraverse(currentStep, true); + } + + public void backward() { + EdgeStep currentStep = this.nextStep(false); + if (currentStep == null) { + return; + } + + this.beforeTraverse(false); + + currentStep.swithDirection(); + // Traversal vertices of previous level + this.traverseOneLayer(this.targets, currentStep, this::backward); + currentStep.swithDirection(); + + this.afterTraverse(currentStep, false); + } + + public abstract EdgeStep nextStep(boolean forward); + + public void beforeTraverse(boolean forward) { + this.clearNewVertices(); + } + + public void traverseOneLayer(Map> vertices, + EdgeStep step, + BiConsumer consumer) { + this.traverseStrategy.traverseOneLayer(vertices, step, consumer); + } + + public void afterTraverse(EdgeStep step, boolean forward) { + this.reInitCurrentStepIfNeeded(step, forward); + this.stepCount++; + } + + private void forward(Id v, EdgeStep step) { + this.traverseOne(v, step, true); + } + + private void backward(Id v, EdgeStep step) { + this.traverseOne(v, step, false); + } + + private void traverseOne(Id v, EdgeStep step, boolean forward) { + if (this.reachLimit()) { + return; + } + + Iterator edges = this.traverser.edgesOfVertex(v, step); + while (edges.hasNext()) { + HugeEdge edge = (HugeEdge) edges.next(); + Id target = edge.id().otherVertexId(); + + this.processOne(v, target, forward); + } + } + + private void processOne(Id source, Id target, boolean forward) { + if (forward) { + this.processOneForForward(source, target); + } else { + this.processOneForBackward(source, target); + } + } + + protected abstract void processOneForForward(Id source, Id target); + + protected abstract void processOneForBackward(Id source, Id target); + + protected abstract void reInitCurrentStepIfNeeded(EdgeStep step, + boolean forward); + + public void clearNewVertices() { + this.newVertices = this.newMultiValueMap(); + } + + public void addNodeToNewVertices(Id id, HugeTraverser.Node node) { + this.addNode(this.newVertices, id, node); + } + + public Map> newMultiValueMap() { + return this.traverseStrategy.newMultiValueMap(); + } + + public Set newPathSet() { + return this.traverseStrategy.newPathSet(); + } + + public void addNode(Map> vertices, Id id, + HugeTraverser.Node node) { + this.traverseStrategy.addNode(vertices, id, node); + } + + public void addNewVerticesToAll(Map> targets) { + this.traverseStrategy.addNewVerticesToAll(this.newVertices, targets); + } + + public Set paths() { + return this.paths; + } + + public int pathCount() { + return this.paths.size(); + } + + protected boolean finished() { + return this.stepCount >= this.totalSteps || this.reachLimit(); + } + + protected boolean reachLimit() { + HugeTraverser.checkCapacity(this.capacity, this.accessedNodes(), + "template paths"); + if (this.limit == NO_LIMIT || this.pathCount() < this.limit) { + return false; + } + return true; + } + + protected int accessedNodes() { + int size = 0; + for (List value : this.sourcesAll.values()) { + size += value.size(); + } + for (List value : this.targetsAll.values()) { + size += value.size(); + } + return size; + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/PathsTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/PathsTraverser.java index cda75814f5..9b4955d001 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/PathsTraverser.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/PathsTraverser.java @@ -19,7 +19,6 @@ package com.baidu.hugegraph.traversal.algorithm; -import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -71,18 +70,14 @@ public PathSet paths(Id sourceV, Directions sourceDir, if (--depth < 0 || traverser.reachLimit()) { break; } - List foundPaths = traverser.forward(sourceDir); - paths.addAll(foundPaths); + traverser.forward(sourceDir); if (--depth < 0 || traverser.reachLimit()) { break; } - foundPaths = traverser.backward(targetDir); - for (Path path : foundPaths) { - path.reverse(); - paths.add(path); - } + traverser.backward(targetDir); } + paths.addAll(traverser.paths()); return paths; } @@ -97,7 +92,7 @@ private class Traverser { private final long degree; private final long capacity; private final long limit; - private long count; + private PathSet paths; public Traverser(Id sourceV, Id targetV, Id label, long degree, long capacity, long limit) { @@ -109,14 +104,13 @@ public Traverser(Id sourceV, Id targetV, Id label, this.degree = degree; this.capacity = capacity; this.limit = limit; - this.count = 0L; + this.paths = new PathSet(); } /** * Search forward from source */ - public List forward(Directions direction) { - List paths = new ArrayList<>(); + public void forward(Directions direction) { MultivaluedMap newVertices = newMultivalueMap(); Iterator edges; // Traversal vertices of previous level @@ -139,10 +133,9 @@ public List forward(Directions direction) { for (Node node : this.targetsAll.get(target)) { List path = n.joinPath(node); if (!path.isEmpty()) { - paths.add(new Path(target, path)); - ++this.count; + this.paths.add(new Path(target, path)); if (this.reachLimit()) { - return paths; + return; } } } @@ -156,16 +149,15 @@ public List forward(Directions direction) { // Re-init sources this.sources = newVertices; // Record all passed vertices - this.sourcesAll.putAll(newVertices); - - return paths; + for (Map.Entry> entry : newVertices.entrySet()) { + this.sourcesAll.addAll(entry.getKey(), entry.getValue()); + } } /** * Search backward from target */ - public List backward(Directions direction) { - List paths = new ArrayList<>(); + public void backward(Directions direction) { MultivaluedMap newVertices = newMultivalueMap(); Iterator edges; // Traversal vertices of previous level @@ -188,10 +180,11 @@ public List backward(Directions direction) { for (Node node : this.sourcesAll.get(target)) { List path = n.joinPath(node); if (!path.isEmpty()) { - paths.add(new Path(target, path)); - ++this.count; + Path newPath = new Path(target, path); + newPath.reverse(); + this.paths.add(newPath); if (this.reachLimit()) { - return paths; + return; } } } @@ -206,9 +199,13 @@ public List backward(Directions direction) { // Re-init targets this.targets = newVertices; // Record all passed vertices - this.targetsAll.putAll(newVertices); + for (Map.Entry> entry : newVertices.entrySet()) { + this.targetsAll.addAll(entry.getKey(), entry.getValue()); + } + } - return paths; + public PathSet paths() { + return this.paths; } private int accessedNodes() { @@ -217,7 +214,7 @@ private int accessedNodes() { private boolean reachLimit() { checkCapacity(this.capacity, this.accessedNodes(), "paths"); - if (this.limit == NO_LIMIT || this.count < this.limit) { + if (this.limit == NO_LIMIT || this.paths.size() < this.limit) { return false; } return true; diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/SameNeighborTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/SameNeighborTraverser.java new file mode 100644 index 0000000000..6cb14b6db8 --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/SameNeighborTraverser.java @@ -0,0 +1,62 @@ +/* + * 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.traversal.algorithm; + +import java.util.Set; + +import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.CollectionUtil; +import com.baidu.hugegraph.util.E; + +public class SameNeighborTraverser extends HugeTraverser { + + public SameNeighborTraverser(HugeGraph graph) { + super(graph); + } + + public Set sameNeighbors(Id vertex, Id other, Directions direction, + String label, long degree, long limit) { + E.checkNotNull(vertex, "vertex id"); + E.checkNotNull(other, "the other vertex id"); + this.checkVertexExist(vertex, "vertex"); + this.checkVertexExist(other, "other vertex"); + E.checkNotNull(direction, "direction"); + checkDegree(degree); + checkLimit(limit); + + Id labelId = this.getEdgeLabelId(label); + + Set sourceNeighbors = IteratorUtils.set(this.adjacentVertices( + vertex, direction, labelId, degree)); + Set targetNeighbors = IteratorUtils.set(this.adjacentVertices( + other, direction, labelId, degree)); + Set sameNeighbors = (Set) CollectionUtil.intersect( + sourceNeighbors, targetNeighbors); + if (limit != NO_LIMIT) { + int end = Math.min(sameNeighbors.size(), (int) limit); + sameNeighbors = CollectionUtil.subSet(sameNeighbors, 0, end); + } + return sameNeighbors; + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/ShortestPathTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/ShortestPathTraverser.java index e72cc96139..aaec88379b 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/ShortestPathTraverser.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/ShortestPathTraverser.java @@ -19,8 +19,11 @@ package com.baidu.hugegraph.traversal.algorithm; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import org.apache.tinkerpop.gremlin.structure.Edge; @@ -40,7 +43,7 @@ public ShortestPathTraverser(HugeGraph graph) { } public Path shortestPath(Id sourceV, Id targetV, Directions dir, - String label, int depth, long degree, + List labels, int depth, long degree, long skipDegree, long capacity) { E.checkNotNull(sourceV, "source vertex id"); E.checkNotNull(targetV, "target vertex id"); @@ -56,8 +59,11 @@ public Path shortestPath(Id sourceV, Id targetV, Directions dir, return new Path(ImmutableList.of(sourceV)); } - Id labelId = this.getEdgeLabelId(label); - Traverser traverser = new Traverser(sourceV, targetV, dir, labelId, + Map labelMap = new HashMap<>(labels.size()); + for (String label : labels) { + labelMap.put(this.getEdgeLabelId(label), label); + } + Traverser traverser = new Traverser(sourceV, targetV, dir, labelMap, degree, skipDegree, capacity); PathSet paths; while (true) { @@ -81,8 +87,15 @@ public Path shortestPath(Id sourceV, Id targetV, Directions dir, return paths.isEmpty() ? Path.EMPTY_PATH : paths.iterator().next(); } + public Path shortestPath(Id sourceV, Id targetV, EdgeStep step, + int depth, long capacity) { + return this.shortestPath(sourceV, targetV, step.direction, + new ArrayList<>(step.labels.values()), + depth, step.degree, step.skipDegree, capacity); + } + public PathSet allShortestPaths(Id sourceV, Id targetV, Directions dir, - String label, int depth, long degree, + List labels, int depth, long degree, long skipDegree, long capacity) { E.checkNotNull(sourceV, "source vertex id"); E.checkNotNull(targetV, "target vertex id"); @@ -100,8 +113,11 @@ public PathSet allShortestPaths(Id sourceV, Id targetV, Directions dir, return paths; } - Id labelId = this.getEdgeLabelId(label); - Traverser traverser = new Traverser(sourceV, targetV, dir, labelId, + Map labelMap = new HashMap<>(labels.size()); + for (String label : labels) { + labelMap.put(this.getEdgeLabelId(label), label); + } + Traverser traverser = new Traverser(sourceV, targetV, dir, labelMap, degree, skipDegree, capacity); while (true) { // Found, reach max depth or reach capacity, stop searching @@ -130,18 +146,19 @@ private class Traverser { private Map targets = newMap(); private final Directions direction; - private final Id label; + private final Map labels; private final long degree; private final long skipDegree; private final long capacity; private long size; - public Traverser(Id sourceV, Id targetV, Directions dir, Id label, - long degree, long skipDegree, long capacity) { + public Traverser(Id sourceV, Id targetV, Directions dir, + Map labels, long degree, + long skipDegree, long capacity) { this.sources.put(sourceV, new Node(sourceV)); this.targets.put(targetV, new Node(targetV)); this.direction = dir; - this.label = label; + this.labels = labels; this.degree = degree; this.skipDegree = skipDegree; this.capacity = capacity; @@ -158,7 +175,7 @@ public PathSet forward(boolean all) { // Traversal vertices of previous level for (Node v : this.sources.values()) { Iterator edges = edgesOfVertex(v.id(), this.direction, - this.label, degree); + this.labels, degree); edges = skipSuperNodeIfNeeded(edges, this.degree, this.skipDegree); while (edges.hasNext()) { @@ -209,7 +226,7 @@ public PathSet backward(boolean all) { // Traversal vertices of previous level for (Node v : this.targets.values()) { Iterator edges = edgesOfVertex(v.id(), opposite, - this.label, degree); + this.labels, degree); edges = skipSuperNodeIfNeeded(edges, this.degree, this.skipDegree); while (edges.hasNext()) { @@ -254,7 +271,7 @@ private boolean superNode(Id vertex, Directions direction) { return false; } Iterator edges = edgesOfVertex(vertex, direction, - this.label, this.skipDegree); + this.labels, this.skipDegree); return IteratorUtils.count(edges) >= this.skipDegree; } } diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/TemplatePathsTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/TemplatePathsTraverser.java new file mode 100644 index 0000000000..d22c01d32f --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/TemplatePathsTraverser.java @@ -0,0 +1,305 @@ +/* + * 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.traversal.algorithm; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.structure.HugeVertex; +import com.baidu.hugegraph.traversal.algorithm.strategy.TraverseStrategy; +import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.E; +import com.baidu.hugegraph.util.Log; + +public class TemplatePathsTraverser extends HugeTraverser { + + private static final Logger LOG = Log.logger(TemplatePathsTraverser.class); + + public TemplatePathsTraverser(HugeGraph graph) { + super(graph); + } + + public Set templatePaths(Iterator sources, + Iterator targets, + List steps, + boolean withRing, + long capacity, long limit) { + checkCapacity(capacity); + checkLimit(limit); + + List sourceList = new ArrayList<>(); + while (sources.hasNext()) { + sourceList.add(((HugeVertex) sources.next()).id()); + } + int sourceSize = sourceList.size(); + E.checkState(sourceSize >= 1 && sourceSize <= MAX_VERTICES, + "The number of source vertices must in [1, %s], " + + "but got: %s", MAX_VERTICES, sourceList.size()); + List targetList = new ArrayList<>(); + while (targets.hasNext()) { + targetList.add(((HugeVertex) targets.next()).id()); + } + int targetSize = targetList.size(); + E.checkState(targetSize >= 1 && targetSize <= MAX_VERTICES, + "The number of target vertices must in [1, %s], " + + "but got: %s", MAX_VERTICES, sourceList.size()); + + int totalSteps = 0; + for (RepeatEdgeStep step : steps) { + totalSteps += step.maxTimes; + } + TraverseStrategy strategy = TraverseStrategy.create( + totalSteps >= this.concurrentDepth(), + this.graph()); + Traverser traverser = new Traverser(this, strategy, + sourceList, targetList, steps, + withRing, capacity, limit); + do { + // Forward + traverser.forward(); + if (traverser.finished()) { + return traverser.paths(); + } + + // Backward + traverser.backward(); + if (traverser.finished()) { + return traverser.paths(); + } + } while (true); + } + + private static class Traverser extends PathTraverser { + + protected final List steps; + + protected boolean withRing; + + protected int sourceIndex; + protected int targetIndex; + + protected boolean sourceFinishOneStep = false; + protected boolean targetFinishOneStep = false; + + public Traverser(HugeTraverser traverser, TraverseStrategy strategy, + Collection sources, Collection targets, + List steps, boolean withRing, + long capacity, long limit) { + super(traverser, strategy, sources, targets, capacity, limit); + + this.steps = steps; + this.withRing = withRing; + for (RepeatEdgeStep step : steps) { + this.totalSteps += step.maxTimes; + } + + this.sourceIndex = 0; + this.targetIndex = this.steps.size() - 1; + + this.sourceFinishOneStep = false; + this.targetFinishOneStep = false; + } + + public RepeatEdgeStep nextStep(boolean forward) { + return forward ? this.forwardStep() : this.backwardStep(); + } + + @Override + public void beforeTraverse(boolean forward) { + this.clearNewVertices(); + this.reInitAllIfNeeded(forward); + } + + @Override + public void afterTraverse(EdgeStep step, boolean forward) { + + Map> all = forward ? this.sourcesAll : + this.targetsAll; + this.addNewVerticesToAll(all); + this.reInitCurrentStepIfNeeded(step, forward); + this.stepCount++; + } + + @Override + protected void processOneForForward(Id sourceV, Id targetV) { + for (Node source : this.sources.get(sourceV)) { + // If have loop, skip target + if (!this.withRing && source.contains(targetV)) { + continue; + } + + // If cross point exists, path found, concat them + if (this.lastSuperStep() && + this.targetsAll.containsKey(targetV)) { + for (Node target : this.targetsAll.get(targetV)) { + List path = joinPath(source, target, this.withRing); + if (!path.isEmpty()) { + this.paths.add(new Path(targetV, path)); + if (this.reachLimit()) { + return; + } + } + } + } + + // Add node to next start-nodes + this.addNodeToNewVertices(targetV, new Node(targetV, source)); + } + } + + @Override + protected void processOneForBackward(Id sourceV, Id targetV) { + for (Node source : this.targets.get(sourceV)) { + // If have loop, skip target + if (!this.withRing && source.contains(targetV)) { + continue; + } + + // If cross point exists, path found, concat them + if (this.lastSuperStep() && + this.sourcesAll.containsKey(targetV)) { + for (Node target : this.sourcesAll.get(targetV)) { + List path = joinPath(source, target, this.withRing); + if (!path.isEmpty()) { + Path newPath = new Path(targetV, path); + newPath.reverse(); + this.paths.add(newPath); + if (this.reachLimit()) { + return; + } + } + } + } + + // Add node to next start-nodes + this.addNodeToNewVertices(targetV, new Node(targetV, source)); + } + } + + private void reInitAllIfNeeded(boolean forward) { + if (forward) { + /* + * Re-init source all if last forward finished one super step + * and current step is not last super step + */ + if (this.sourceFinishOneStep && !this.lastSuperStep()) { + this.sourcesAll = this.newMultiValueMap(); + this.sourceFinishOneStep = false; + } + } else { + /* + * Re-init target all if last forward finished one super step + * and current step is not last super step + */ + if (this.targetFinishOneStep && !this.lastSuperStep()) { + this.targetsAll = this.newMultiValueMap(); + this.targetFinishOneStep = false; + } + } + } + + @Override + protected void reInitCurrentStepIfNeeded(EdgeStep step, + boolean forward) { + RepeatEdgeStep currentStep = (RepeatEdgeStep) step; + currentStep.decreaseTimes(); + if (forward) { + // Re-init sources + if (currentStep.remainTimes() > 0) { + this.sources = this.newVertices; + } else { + this.sources = this.sourcesAll; + this.sourceFinishOneStep = true; + } + } else { + // Re-init targets + if (currentStep.remainTimes() > 0) { + this.targets = this.newVertices; + } else { + this.targets = this.targetsAll; + this.targetFinishOneStep = true; + } + } + } + + public RepeatEdgeStep forwardStep() { + RepeatEdgeStep currentStep = null; + // Find next step to backward + for (int i = 0; i < this.steps.size(); i++) { + RepeatEdgeStep step = this.steps.get(i); + if (step.remainTimes() > 0) { + currentStep = step; + this.targetIndex = i; + break; + } + } + return currentStep; + } + + public RepeatEdgeStep backwardStep() { + RepeatEdgeStep currentStep = null; + // Find next step to backward + for (int i = this.steps.size() - 1; i >= 0; i--) { + RepeatEdgeStep step = this.steps.get(i); + if (step.remainTimes() > 0) { + currentStep = step; + this.targetIndex = i; + break; + } + } + return currentStep; + } + + public boolean lastSuperStep() { + return this.targetIndex == this.sourceIndex || + this.targetIndex == this.sourceIndex + 1; + } + } + + public static class RepeatEdgeStep extends EdgeStep { + + private int maxTimes = 1; + + public RepeatEdgeStep(HugeGraph g, Directions direction, + List labels, + Map properties, long degree, + long skipDegree, int maxTimes) { + super(g, direction, labels, properties, degree, skipDegree); + this.maxTimes = maxTimes; + } + + private int remainTimes() { + return this.maxTimes; + } + + private void decreaseTimes() { + this.maxTimes--; + } + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/strategy/ConcurrentTraverseStrategy.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/strategy/ConcurrentTraverseStrategy.java new file mode 100644 index 0000000000..f856e0a47c --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/strategy/ConcurrentTraverseStrategy.java @@ -0,0 +1,74 @@ +/* + * 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.traversal.algorithm.strategy; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.traversal.algorithm.EdgeStep; +import com.baidu.hugegraph.traversal.algorithm.OltpTraverser; + +public class ConcurrentTraverseStrategy extends OltpTraverser + implements TraverseStrategy { + + public ConcurrentTraverseStrategy(HugeGraph graph) { + super(graph); + } + + @Override + public Map> newMultiValueMap() { + return new OltpTraverser.ConcurrentMultiValuedMap<>(); + } + + @Override + public void traverseOneLayer(Map> vertices, + EdgeStep step, + BiConsumer biConsumer) { + traverseIds(vertices.keySet().iterator(), (id) -> { + biConsumer.accept(id, step); + }); + } + + @Override + public Set newPathSet() { + return ConcurrentHashMap.newKeySet(); + } + + @Override + public void addNode(Map> vertices, + Id id, Node node) { + ((ConcurrentMultiValuedMap) vertices).add(id, node); + } + + @Override + public void addNewVerticesToAll(Map> newVertices, + Map> targets) { + ConcurrentMultiValuedMap vertices = + (ConcurrentMultiValuedMap) targets; + for (Map.Entry> entry : newVertices.entrySet()) { + vertices.addAll(entry.getKey(), entry.getValue()); + } + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/strategy/SingleTraverseStrategy.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/strategy/SingleTraverseStrategy.java new file mode 100644 index 0000000000..822949d185 --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/strategy/SingleTraverseStrategy.java @@ -0,0 +1,75 @@ +/* + * 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.traversal.algorithm.strategy; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; + +import javax.ws.rs.core.MultivaluedMap; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.traversal.algorithm.EdgeStep; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; +import com.baidu.hugegraph.traversal.algorithm.OltpTraverser; + +public class SingleTraverseStrategy extends OltpTraverser + implements TraverseStrategy { + + public SingleTraverseStrategy(HugeGraph graph) { + super(graph); + } + + @Override + public void traverseOneLayer(Map> vertices, + EdgeStep step, + BiConsumer biConsumer) { + for (Id id : vertices.keySet()) { + biConsumer.accept(id, step); + } + } + + @Override + public Map> newMultiValueMap() { + return newMultivalueMap(); + } + + @Override + public Set newPathSet() { + return new HugeTraverser.PathSet(); + } + + @Override + public void addNode(Map> vertices, Id id, Node node) { + ((MultivaluedMap) vertices).add(id, node); + } + + @Override + public void addNewVerticesToAll(Map> newVertices, + Map> targets) { + MultivaluedMap vertices = + (MultivaluedMap) targets; + for (Map.Entry> entry : newVertices.entrySet()) { + vertices.addAll(entry.getKey(), entry.getValue()); + } + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/strategy/TraverseStrategy.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/strategy/TraverseStrategy.java new file mode 100644 index 0000000000..cb81ad87f8 --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/strategy/TraverseStrategy.java @@ -0,0 +1,53 @@ +/* + * 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.traversal.algorithm.strategy; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.Id; +import com.baidu.hugegraph.traversal.algorithm.EdgeStep; +import com.baidu.hugegraph.traversal.algorithm.HugeTraverser; + +public interface TraverseStrategy { + + public abstract void traverseOneLayer( + Map> vertices, + EdgeStep step, BiConsumer consumer); + + public abstract Map> newMultiValueMap(); + + public abstract Set newPathSet(); + + public abstract void addNode(Map> vertices, + Id id, HugeTraverser.Node node); + + public abstract void addNewVerticesToAll( + Map> newVertices, + Map> targets); + + public static TraverseStrategy create(boolean concurrent, HugeGraph graph) { + return concurrent ? new ConcurrentTraverseStrategy(graph) : + new SingleTraverseStrategy(graph); + } +}