diff --git a/java/pom.xml b/java/pom.xml index 9322d6a..234b3b9 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -1,361 +1,420 @@ <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.softwareheritage.graph</groupId> <artifactId>swh-graph</artifactId> <version>${git.closest.tag.name}</version> <name>swh-graph</name> <url>https://forge.softwareheritage.org/source/swh-graph/</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.release>11</maven.compiler.release> + <protobuf.version>3.20.1</protobuf.version> + <grpc.version>1.46.0</grpc.version> </properties> <dependencies> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.7.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> <version>5.7.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.7.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest</artifactId> <version>2.2</version> <scope>test</scope> </dependency> <dependency> <groupId>io.javalin</groupId> <artifactId>javalin</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.26</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.0</version> </dependency> <dependency> <groupId>it.unimi.dsi</groupId> <artifactId>webgraph-big</artifactId> <version>3.6.7</version> </dependency> <dependency> <groupId>it.unimi.dsi</groupId> <artifactId>fastutil</artifactId> <version>8.5.8</version> </dependency> <dependency> <groupId>it.unimi.dsi</groupId> <artifactId>dsiutils</artifactId> <version>2.7.1</version> </dependency> <dependency> <groupId>it.unimi.dsi</groupId> <artifactId>sux4j</artifactId> <version>5.3.1</version> </dependency> <dependency> <groupId>it.unimi.dsi</groupId> <artifactId>law</artifactId> <version>2.7.2</version> <exclusions> <exclusion> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> </exclusion> <exclusion> <groupId>org.umlgraph</groupId> <artifactId>umlgraph</artifactId> </exclusion> <exclusion> <groupId>org.eclipse.jetty.aggregate</groupId> <artifactId>jetty-all</artifactId> </exclusion> <exclusion> <groupId>it.unimi.di</groupId> <artifactId>mg4j</artifactId> </exclusion> <exclusion> <groupId>it.unimi.di</groupId> <artifactId>mg4j-big</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.martiansoftware</groupId> <artifactId>jsap</artifactId> <version>2.1</version> </dependency> <dependency> <groupId>net.sf.py4j</groupId> <artifactId>py4j</artifactId> <version>0.10.9.3</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.15</version> </dependency> <dependency> <groupId>com.github.luben</groupId> <artifactId>zstd-jni</artifactId> <version>1.5.1-1</version> </dependency> <dependency> <groupId>org.apache.orc</groupId> <artifactId>orc-core</artifactId> <version>1.7.1</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> <version>3.3.1</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client-runtime</artifactId> <version>3.3.1</version> </dependency> + <dependency> + <groupId>com.google.protobuf</groupId> + <artifactId>protobuf-java</artifactId> + <version>${protobuf.version}</version> + </dependency> + <dependency> + <groupId>io.grpc</groupId> + <artifactId>grpc-netty-shaded</artifactId> + <version>${grpc.version}</version> + </dependency> + <dependency> + <groupId>io.grpc</groupId> + <artifactId>grpc-protobuf</artifactId> + <version>${grpc.version}</version> + </dependency> + <dependency> + <groupId>io.grpc</groupId> + <artifactId>grpc-stub</artifactId> + <version>${grpc.version}</version> + </dependency> + <dependency> + <groupId>io.grpc</groupId> + <artifactId>grpc-services</artifactId> + <version>${grpc.version}</version> + </dependency> + <dependency> + <groupId>javax.annotation</groupId> + <artifactId>javax.annotation-api</artifactId> + <version>1.3.2</version> + </dependency> </dependencies> <build> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle --> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <source>11</source> <target>11</target> <compilerArgs> <arg>-verbose</arg> <arg>-Xlint:all</arg> </compilerArgs> </configuration> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> </plugin> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <version>2.22.2</version> </plugin> <plugin> <artifactId>maven-jar-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle --> <plugin> <artifactId>maven-site-plugin</artifactId> <version>3.7.1</version> </plugin> <plugin> <artifactId>maven-project-info-reports-plugin</artifactId> <version>3.0.0</version> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>3.3.0</version> <configuration> <archive> <manifest> <mainClass>org.softwareheritage.graph.server.App</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <appendAssemblyId>false</appendAssemblyId> </configuration> <executions> <execution> <id>make-assembly</id> <!-- this is used for inheritance merges --> <phase>package</phase> <!-- bind to the packaging phase --> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> <!-- Spotless code formatting tool --> <plugin> <groupId>com.diffplug.spotless</groupId> <artifactId>spotless-maven-plugin</artifactId> <version>2.22.1</version> <configuration> <formats> <format> <includes> <include>*.md</include> <include>.gitignore</include> </includes> <trimTrailingWhitespace/> <endWithNewline/> <indent> <spaces>true</spaces> <spacesPerTab>4</spacesPerTab> </indent> </format> </formats> <java> <removeUnusedImports/> <eclipse> <version>4.16.0</version> <file>.coding-style.xml</file> </eclipse> </java> </configuration> </plugin> </plugins> </pluginManagement> <plugins> <plugin> <groupId>pl.project13.maven</groupId> <artifactId>git-commit-id-plugin</artifactId> <version>3.0.1</version> <executions> <execution> <id>get-the-git-infos</id> <goals> <goal>revision</goal> </goals> <phase>initialize</phase> </execution> </executions> <configuration> <verbose>true</verbose> <offline>true</offline> <useNativeGit>true</useNativeGit> <gitDescribe> <always>true</always> <match>v*</match> </gitDescribe> <replacementProperties> <replacementProperty> <property>git.closest.tag.name</property> <token>^v</token> <value></value> <regex>true</regex> </replacementProperty> </replacementProperties> </configuration> </plugin> <plugin> <artifactId>maven-source-plugin</artifactId> <version>2.1.1</version> <executions> <execution> <id>bundle-sources</id> <phase>package</phase> <goals> <goal>jar-no-fork</goal> <goal>test-jar-no-fork</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <version>3.3.1</version> <executions> <execution> <id>resource-bundles</id> <phase>package</phase> <goals> <!-- produce source artifact for main project sources --> <goal>resource-bundle</goal> <!-- produce source artifact for project test sources --> <goal>test-resource-bundle</goal> </goals> <configuration> <detectOfflineLinks>false</detectOfflineLinks> </configuration> </execution> <execution> <id>javadoc-jar</id> <phase>package</phase> <goals> <goal>jar</goal> </goals> </execution> </executions> <configuration> <includeDependencySources>true</includeDependencySources> <dependencySourceIncludes> <dependencySourceInclude>it.unimi.dsi:webgraph-big:*</dependencySourceInclude> </dependencySourceIncludes> <links> <link>https://webgraph.di.unimi.it/docs-big/</link> <link>https://dsiutils.di.unimi.it/docs/</link> <link>https://fastutil.di.unimi.it/docs/</link> <link>https://law.di.unimi.it/software/law-docs/</link> </links> <tags> <tag> <name>implSpec</name> <placement>a</placement> <head>Implementation Requirements:</head> </tag> <tag> <name>implNote</name> <placement>a</placement> <head>Implementation Note:</head> </tag> </tags> </configuration> </plugin> + <plugin> + <groupId>org.xolstice.maven.plugins</groupId> + <artifactId>protobuf-maven-plugin</artifactId> + <version>0.6.1</version> + <configuration> + <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact> + <pluginId>grpc-java</pluginId> + <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + <goal>compile-custom</goal> + <goal>test-compile</goal> + <goal>test-compile-custom</goal> + </goals> + </execution> + </executions> + </plugin> </plugins> + <extensions> + <extension> + <groupId>kr.motd.maven</groupId> + <artifactId>os-maven-plugin</artifactId> + <version>1.6.2</version> + </extension> + </extensions> </build> </project> diff --git a/java/src/main/java/org/softwareheritage/graph/rpc/GraphServer.java b/java/src/main/java/org/softwareheritage/graph/rpc/GraphServer.java new file mode 100644 index 0000000..502e4ac --- /dev/null +++ b/java/src/main/java/org/softwareheritage/graph/rpc/GraphServer.java @@ -0,0 +1,157 @@ +package org.softwareheritage.graph.rpc; + +import com.martiansoftware.jsap.*; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.stub.StreamObserver; +import io.grpc.protobuf.services.ProtoReflectionService; +import it.unimi.dsi.logging.ProgressLogger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.softwareheritage.graph.SwhBidirectionalGraph; +import org.softwareheritage.graph.compress.LabelMapBuilder; + +import java.io.IOException; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Server that manages startup/shutdown of a {@code Greeter} server. + */ +public class GraphServer { + private final static Logger logger = LoggerFactory.getLogger(GraphServer.class); + + private final SwhBidirectionalGraph graph; + private final int port; + private final int threads; + private Server server; + + public GraphServer(String graphBasename, int port, int threads) throws IOException { + this.graph = SwhBidirectionalGraph.loadLabelledMapped(graphBasename, new ProgressLogger(logger)); + this.port = port; + this.threads = threads; + graph.loadContentLength(); + graph.loadContentIsSkipped(); + graph.loadPersonIds(); + graph.loadAuthorTimestamps(); + graph.loadCommitterTimestamps(); + graph.loadMessages(); + graph.loadTagNames(); + graph.loadLabelNames(); + } + + private void start() throws IOException { + server = ServerBuilder.forPort(port).executor(Executors.newFixedThreadPool(threads)) + .addService(new TraversalService(graph)).addService(ProtoReflectionService.newInstance()).build() + .start(); + logger.info("Server started, listening on " + port); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + GraphServer.this.stop(); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + })); + } + + private void stop() throws InterruptedException { + if (server != null) { + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); + } + } + + /** + * Await termination on the main thread since the grpc library uses daemon threads. + */ + private void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + private static JSAPResult parseArgs(String[] args) { + JSAPResult config = null; + try { + SimpleJSAP jsap = new SimpleJSAP(LabelMapBuilder.class.getName(), "", + new Parameter[]{ + new FlaggedOption("port", JSAP.INTEGER_PARSER, "50091", JSAP.NOT_REQUIRED, 'p', "port", + "The port on which the server should listen."), + new FlaggedOption("threads", JSAP.INTEGER_PARSER, "1", JSAP.NOT_REQUIRED, 't', "threads", + "The number of concurrent threads. 0 = number of cores."), + new UnflaggedOption("graphBasename", JSAP.STRING_PARSER, JSAP.REQUIRED, + "Basename of the output graph")}); + + config = jsap.parse(args); + if (jsap.messagePrinted()) { + System.exit(1); + } + } catch (JSAPException e) { + e.printStackTrace(); + } + return config; + } + + /** + * Main launches the server from the command line. + */ + public static void main(String[] args) throws IOException, InterruptedException { + JSAPResult config = parseArgs(args); + String graphBasename = config.getString("graphBasename"); + int port = config.getInt("port"); + int threads = config.getInt("threads"); + if (threads == 0) { + threads = Runtime.getRuntime().availableProcessors(); + } + + final GraphServer server = new GraphServer(graphBasename, port, threads); + server.start(); + server.blockUntilShutdown(); + } + + static class TraversalService extends TraversalServiceGrpc.TraversalServiceImplBase { + SwhBidirectionalGraph graph; + + public TraversalService(SwhBidirectionalGraph graph) { + this.graph = graph; + } + + @Override + public void traverse(TraversalRequest request, StreamObserver<Node> responseObserver) { + SwhBidirectionalGraph g = graph.copy(); + Traversal.simpleTraversal(g, request, responseObserver::onNext); + responseObserver.onCompleted(); + } + + @Override + public void countNodes(TraversalRequest request, StreamObserver<CountResponse> responseObserver) { + AtomicInteger count = new AtomicInteger(0); + SwhBidirectionalGraph g = graph.copy(); + TraversalRequest fixedReq = TraversalRequest.newBuilder(request) + // Ignore return fields, just count nodes + .setReturnFields(NodeFields.getDefaultInstance()).build(); + Traversal.simpleTraversal(g, fixedReq, (Node node) -> { + count.incrementAndGet(); + }); + CountResponse response = CountResponse.newBuilder().setCount(count.get()).build(); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + @Override + public void countEdges(TraversalRequest request, StreamObserver<CountResponse> responseObserver) { + AtomicInteger count = new AtomicInteger(0); + SwhBidirectionalGraph g = graph.copy(); + TraversalRequest fixedReq = TraversalRequest.newBuilder(request) + // Force return empty successors to count the edges + .setReturnFields(NodeFields.newBuilder().setSuccessor(true).setSuccessorSwhid(false).build()) + .build(); + Traversal.simpleTraversal(g, fixedReq, (Node node) -> { + count.addAndGet(node.getSuccessorCount()); + }); + CountResponse response = CountResponse.newBuilder().setCount(count.get()).build(); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + } +} diff --git a/java/src/main/java/org/softwareheritage/graph/rpc/Traversal.java b/java/src/main/java/org/softwareheritage/graph/rpc/Traversal.java new file mode 100644 index 0000000..e046476 --- /dev/null +++ b/java/src/main/java/org/softwareheritage/graph/rpc/Traversal.java @@ -0,0 +1,275 @@ +package org.softwareheritage.graph.rpc; + +import com.google.protobuf.ByteString; +import it.unimi.dsi.big.webgraph.LazyLongIterator; +import it.unimi.dsi.big.webgraph.labelling.ArcLabelledNodeIterator; +import it.unimi.dsi.big.webgraph.labelling.Label; +import org.softwareheritage.graph.*; +import org.softwareheritage.graph.labels.DirEntry; + +import java.util.*; + +public class Traversal { + private static LazyLongIterator filterSuccessors(SwhUnidirectionalGraph g, long nodeId, AllowedEdges allowedEdges) { + if (allowedEdges.restrictedTo == null) { + // All edges are allowed, bypass edge check + return g.successors(nodeId); + } else { + LazyLongIterator allSuccessors = g.successors(nodeId); + return new LazyLongIterator() { + @Override + public long nextLong() { + long neighbor; + while ((neighbor = allSuccessors.nextLong()) != -1) { + if (allowedEdges.isAllowed(g.getNodeType(nodeId), g.getNodeType(neighbor))) { + return neighbor; + } + } + return -1; + } + + @Override + public long skip(final long n) { + long i = 0; + while (i < n && nextLong() != -1) + i++; + return i; + } + }; + } + } + + private static ArcLabelledNodeIterator.LabelledArcIterator filterLabelledSuccessors(SwhUnidirectionalGraph g, + long nodeId, AllowedEdges allowedEdges) { + if (allowedEdges.restrictedTo == null) { + // All edges are allowed, bypass edge check + return g.labelledSuccessors(nodeId); + } else { + ArcLabelledNodeIterator.LabelledArcIterator allSuccessors = g.labelledSuccessors(nodeId); + return new ArcLabelledNodeIterator.LabelledArcIterator() { + @Override + public Label label() { + return allSuccessors.label(); + } + + @Override + public long nextLong() { + long neighbor; + while ((neighbor = allSuccessors.nextLong()) != -1) { + if (allowedEdges.isAllowed(g.getNodeType(nodeId), g.getNodeType(neighbor))) { + return neighbor; + } + } + return -1; + } + + @Override + public long skip(final long n) { + long i = 0; + while (i < n && nextLong() != -1) + i++; + return i; + } + }; + } + } + + private static class NodeFilterChecker { + private final SwhUnidirectionalGraph g; + private final NodeFilter filter; + private final AllowedNodes allowedNodes; + + private NodeFilterChecker(SwhUnidirectionalGraph graph, NodeFilter filter) { + this.g = graph; + this.filter = filter; + this.allowedNodes = new AllowedNodes(filter.hasTypes() ? filter.getTypes() : "*"); + } + + public boolean allowed(long nodeId) { + if (filter == null) { + return true; + } + if (!this.allowedNodes.isAllowed(g.getNodeType(nodeId))) { + return false; + } + + long outdegree = g.outdegree(nodeId); + if (filter.hasMinTraversalSuccessors() && outdegree < filter.getMinTraversalSuccessors()) { + return false; + } + if (filter.hasMaxTraversalSuccessors() && outdegree > filter.getMaxTraversalSuccessors()) { + return false; + } + + return true; + } + } + + public static SwhUnidirectionalGraph getDirectedGraph(SwhBidirectionalGraph g, TraversalRequest request) { + switch (request.getDirection()) { + case FORWARD: + return g.getForwardGraph(); + case BACKWARD: + return g.getBackwardGraph(); + case BOTH: + return new SwhUnidirectionalGraph(g.symmetrize(), g.getProperties()); + } + throw new IllegalArgumentException("Unknown direction: " + request.getDirection()); + } + + public static void simpleTraversal(SwhBidirectionalGraph bidirectionalGraph, TraversalRequest request, + NodeObserver nodeObserver) { + SwhUnidirectionalGraph g = getDirectedGraph(bidirectionalGraph, request); + NodeFilterChecker nodeReturnChecker = new NodeFilterChecker(g, request.getReturnNodes()); + + AllowedEdges allowedEdges = new AllowedEdges(request.hasEdges() ? request.getEdges() : "*"); + + Queue<Long> queue = new ArrayDeque<>(); + HashSet<Long> visited = new HashSet<>(); + request.getSrcList().forEach(srcSwhid -> { + long srcNodeId = g.getNodeId(new SWHID(srcSwhid)); + queue.add(srcNodeId); + visited.add(srcNodeId); + }); + queue.add(-1L); // Depth sentinel + + long edgesAccessed = 0; + long currentDepth = 0; + while (!queue.isEmpty()) { + long curr = queue.poll(); + if (curr == -1L) { + ++currentDepth; + if (!queue.isEmpty()) { + queue.add(-1L); + } + continue; + } + if (request.hasMaxDepth() && currentDepth > request.getMaxDepth()) { + break; + } + edgesAccessed += g.outdegree(curr); + if (request.hasMaxEdges() && edgesAccessed >= request.getMaxEdges()) { + break; + } + + Node.Builder nodeBuilder = null; + if (nodeReturnChecker.allowed(curr)) { + nodeBuilder = Node.newBuilder(); + buildNodeProperties(g, request.getReturnFields(), nodeBuilder, curr); + } + + ArcLabelledNodeIterator.LabelledArcIterator it = filterLabelledSuccessors(g, curr, allowedEdges); + for (long succ; (succ = it.nextLong()) != -1;) { + if (!visited.contains(succ)) { + queue.add(succ); + visited.add(succ); + } + buildSuccessorProperties(g, request.getReturnFields(), nodeBuilder, curr, succ, it.label()); + } + if (nodeBuilder != null) { + nodeObserver.onNext(nodeBuilder.build()); + } + } + } + + private static void buildNodeProperties(SwhUnidirectionalGraph graph, NodeFields fields, Node.Builder nodeBuilder, + long node) { + if (fields == null || !fields.hasSwhid() || fields.getSwhid()) { + nodeBuilder.setSwhid(graph.getSWHID(node).toString()); + } + if (fields == null) { + return; + } + + switch (graph.getNodeType(node)) { + case CNT: + if (fields.hasCntLength()) { + nodeBuilder.setCntLength(graph.getContentLength(node)); + } + if (fields.hasCntIsSkipped()) { + nodeBuilder.setCntIsSkipped(graph.isContentSkipped(node)); + } + break; + case REV: + if (fields.getRevAuthor()) { + nodeBuilder.setRevAuthor(graph.getAuthorId(node)); + } + if (fields.getRevCommitter()) { + nodeBuilder.setRevAuthor(graph.getCommitterId(node)); + } + if (fields.getRevAuthorDate()) { + nodeBuilder.setRevAuthorDate(graph.getAuthorTimestamp(node)); + } + if (fields.getRevAuthorDateOffset()) { + nodeBuilder.setRevAuthorDateOffset(graph.getAuthorTimestampOffset(node)); + } + if (fields.getRevCommitterDate()) { + nodeBuilder.setRevCommitterDate(graph.getCommitterTimestamp(node)); + } + if (fields.getRevCommitterDateOffset()) { + nodeBuilder.setRevCommitterDateOffset(graph.getCommitterTimestampOffset(node)); + } + if (fields.getRevMessage()) { + byte[] msg = graph.getMessage(node); + if (msg != null) { + nodeBuilder.setRevMessage(ByteString.copyFrom(msg)); + } + } + break; + case REL: + if (fields.getRelAuthor()) { + nodeBuilder.setRelAuthor(graph.getAuthorId(node)); + } + if (fields.getRelAuthorDate()) { + nodeBuilder.setRelAuthorDate(graph.getAuthorTimestamp(node)); + } + if (fields.getRelAuthorDateOffset()) { + nodeBuilder.setRelAuthorDateOffset(graph.getAuthorTimestampOffset(node)); + } + if (fields.getRelName()) { + byte[] msg = graph.getTagName(node); + if (msg != null) { + nodeBuilder.setRelName(ByteString.copyFrom(msg)); + } + } + if (fields.getRelMessage()) { + byte[] msg = graph.getMessage(node); + if (msg != null) { + nodeBuilder.setRelMessage(ByteString.copyFrom(msg)); + } + } + break; + case ORI: + if (fields.getOriUrl()) { + String url = graph.getUrl(node); + if (url != null) { + nodeBuilder.setOriUrl(url); + } + } + } + } + + private static void buildSuccessorProperties(SwhUnidirectionalGraph graph, NodeFields fields, + Node.Builder nodeBuilder, long src, long dst, Label label) { + if (nodeBuilder != null && fields != null && fields.getSuccessor()) { + Successor.Builder successorBuilder = Successor.newBuilder(); + if (!fields.hasSuccessorSwhid() || fields.getSuccessorSwhid()) { + successorBuilder.setSwhid(graph.getSWHID(dst).toString()); + } + if (fields.getSuccessorLabel()) { + DirEntry[] entries = (DirEntry[]) label.get(); + for (DirEntry entry : entries) { + EdgeLabel.Builder builder = EdgeLabel.newBuilder(); + builder.setName(ByteString.copyFrom(graph.getLabelName(entry.filenameId))); + builder.setPermission(entry.permission); + successorBuilder.addLabel(builder.build()); + } + } + nodeBuilder.addSuccessor(successorBuilder.build()); + } + } + + public interface NodeObserver { + void onNext(Node nodeId); + } +} diff --git a/proto/swhgraph.proto b/proto/swhgraph.proto new file mode 100644 index 0000000..0986234 --- /dev/null +++ b/proto/swhgraph.proto @@ -0,0 +1,102 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "org.softwareheritage.graph.rpc"; +option java_outer_classname = "GraphService"; + +package swh.graph; + +service TraversalService { + rpc Traverse (TraversalRequest) returns (stream Node); + rpc CountNodes (TraversalRequest) returns (CountResponse); + rpc CountEdges (TraversalRequest) returns (CountResponse); +} + +enum GraphDirection { + FORWARD = 0; + BACKWARD = 1; + BOTH = 2; +} + +message TraversalRequest { + repeated string src = 1; + + // Traversal options + optional GraphDirection direction = 2; + optional string edges = 3; + optional int64 max_edges = 4; + optional int64 max_depth = 5; + optional NodeFilter return_nodes = 6; + optional NodeFields return_fields = 7; +} + +message NodeFilter { + optional string types = 1; + optional int64 min_traversal_successors = 2; + optional int64 max_traversal_successors = 3; +} + +message NodeFields { + optional bool swhid = 1; + + optional bool successor = 2; + optional bool successor_swhid = 3; + optional bool successor_label = 4; + + optional bool cnt_length = 5; + optional bool cnt_is_skipped = 6; + + optional bool rev_author = 7; + optional bool rev_author_date = 8; + optional bool rev_author_date_offset = 9; + optional bool rev_committer = 10; + optional bool rev_committer_date = 11; + optional bool rev_committer_date_offset = 12; + optional bool rev_message = 13; + + optional bool rel_author = 14; + optional bool rel_author_date = 15; + optional bool rel_author_date_offset = 16; + optional bool rel_name = 17; + optional bool rel_message = 18; + + optional bool ori_url = 19; +} + +message Node { + string swhid = 1; + repeated Successor successor = 2; + + optional int64 cnt_length = 3; + optional bool cnt_is_skipped = 4; + + optional int64 rev_author = 5; + optional int64 rev_author_date = 6; + optional int32 rev_author_date_offset = 7; + optional int64 rev_committer = 8; + optional int64 rev_committer_date = 9; + optional int32 rev_committer_date_offset = 10; + optional bytes rev_message = 11; + + optional int64 rel_author = 12; + optional int64 rel_author_date = 13; + optional int32 rel_author_date_offset = 14; + optional bytes rel_name = 15; + optional bytes rel_message = 16; + + optional string ori_url = 17; +} + +message Successor { + optional string swhid = 1; + repeated EdgeLabel label = 2; +} + +message EdgeLabel { + bytes name = 1; + int32 permission = 2; +} + +message CountResponse { + int64 count = 1; +}