Page MenuHomeSoftware Heritage

App.java
No OneTemporary

App.java

package org.softwareheritage.graph.server;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.martiansoftware.jsap.*;
import io.javalin.Javalin;
import io.javalin.http.Context;
import io.javalin.plugin.json.JavalinJackson;
import org.softwareheritage.graph.Graph;
import org.softwareheritage.graph.Stats;
import org.softwareheritage.graph.SWHID;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* Web framework of the swh-graph server RPC API.
*
* @author The Software Heritage developers
*/
public class App {
/**
* Main entrypoint.
*
* @param args command line arguments
*/
public static void main(String[] args) throws IOException, JSAPException {
SimpleJSAP jsap = new SimpleJSAP(App.class.getName(),
"Server to load and query a compressed graph representation of Software Heritage archive.",
new Parameter[]{
new FlaggedOption("port", JSAP.INTEGER_PARSER, "5009", JSAP.NOT_REQUIRED, 'p', "port",
"Binding port of the server."),
new UnflaggedOption("graphPath", JSAP.STRING_PARSER, JSAP.NO_DEFAULT, JSAP.REQUIRED,
JSAP.NOT_GREEDY, "The basename of the compressed graph."),
new Switch("timings", 't', "timings", "Show timings in API result metadata."),});
JSAPResult config = jsap.parse(args);
if (jsap.messagePrinted()) {
System.exit(1);
}
String graphPath = config.getString("graphPath");
int port = config.getInt("port");
boolean showTimings = config.getBoolean("timings");
startServer(graphPath, port, showTimings);
}
/**
* Loads compressed graph and starts the web server to query it.
*
* @param graphPath basename of the compressed graph
* @param port binding port of the server
* @param showTimings true if timings should be in results metadata, false otherwise
*/
private static void startServer(String graphPath, int port, boolean showTimings) throws IOException {
Graph graph = Graph.loadMapped(graphPath);
Stats stats = new Stats(graphPath);
// Clean up on exit
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try {
graph.cleanUp();
} catch (IOException e) {
System.out.println("Could not clean up graph on exit: " + e);
}
}
});
// Configure Jackson JSON to use snake case naming style
ObjectMapper objectMapper = JavalinJackson.getObjectMapper();
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
JavalinJackson.configure(objectMapper);
Javalin app = Javalin.create().start(port);
app.before("/stats/*", ctx -> {
checkQueryStrings(ctx, "");
});
app.before("/leaves/*", ctx -> {
checkQueryStrings(ctx, "direction|edges");
});
app.before("/neighbors/*", ctx -> {
checkQueryStrings(ctx, "direction|edges");
});
app.before("/visit/*", ctx -> {
checkQueryStrings(ctx, "direction|edges");
});
app.before("/walk/*", ctx -> {
checkQueryStrings(ctx, "direction|edges|traversal");
});
app.get("/stats/", ctx -> {
ctx.json(stats);
});
// Graph traversal endpoints
// By default the traversal is a forward DFS using all edges
app.get("/leaves/:src", ctx -> {
SWHID src = new SWHID(ctx.pathParam("src"));
String direction = ctx.queryParam("direction", "forward");
String edgesFmt = ctx.queryParam("edges", "*");
Endpoint endpoint = new Endpoint(graph, direction, edgesFmt);
Endpoint.Output output = endpoint.leaves(new Endpoint.Input(src));
ctx.json(formatEndpointOutput(output, showTimings));
});
app.get("/neighbors/:src", ctx -> {
SWHID src = new SWHID(ctx.pathParam("src"));
String direction = ctx.queryParam("direction", "forward");
String edgesFmt = ctx.queryParam("edges", "*");
Endpoint endpoint = new Endpoint(graph, direction, edgesFmt);
Endpoint.Output output = endpoint.neighbors(new Endpoint.Input(src));
ctx.json(formatEndpointOutput(output, showTimings));
});
app.get("/visit/nodes/:src", ctx -> {
SWHID src = new SWHID(ctx.pathParam("src"));
String direction = ctx.queryParam("direction", "forward");
String edgesFmt = ctx.queryParam("edges", "*");
Endpoint endpoint = new Endpoint(graph, direction, edgesFmt);
Endpoint.Output output = endpoint.visitNodes(new Endpoint.Input(src));
ctx.json(formatEndpointOutput(output, showTimings));
});
app.get("/visit/paths/:src", ctx -> {
SWHID src = new SWHID(ctx.pathParam("src"));
String direction = ctx.queryParam("direction", "forward");
String edgesFmt = ctx.queryParam("edges", "*");
Endpoint endpoint = new Endpoint(graph, direction, edgesFmt);
Endpoint.Output output = endpoint.visitPaths(new Endpoint.Input(src));
ctx.json(formatEndpointOutput(output, showTimings));
});
app.get("/walk/:src/:dst", ctx -> {
SWHID src = new SWHID(ctx.pathParam("src"));
String dstFmt = ctx.pathParam("dst");
String direction = ctx.queryParam("direction", "forward");
String edgesFmt = ctx.queryParam("edges", "*");
String algorithm = ctx.queryParam("traversal", "dfs");
Endpoint endpoint = new Endpoint(graph, direction, edgesFmt);
Endpoint.Output output = endpoint.walk(new Endpoint.Input(src, dstFmt, algorithm));
ctx.json(formatEndpointOutput(output, showTimings));
});
app.exception(IllegalArgumentException.class, (e, ctx) -> {
ctx.status(400);
ctx.result(e.getMessage());
});
}
/**
* Checks query strings names provided to the RPC API.
*
* @param ctx Javalin HTTP request context
* @param allowedFmt a regular expression describing allowed query strings names
* @throws IllegalArgumentException unknown query string provided
*/
private static void checkQueryStrings(Context ctx, String allowedFmt) {
Map<String, List<String>> queryParamMap = ctx.queryParamMap();
for (String key : queryParamMap.keySet()) {
if (!key.matches(allowedFmt)) {
throw new IllegalArgumentException("Unknown query string: " + key);
}
}
}
/**
* Formats endpoint result into final JSON for the RPC API.
* <p>
* Removes unwanted information if necessary, such as timings (to prevent use of side channels
* attacks).
*
* @param output endpoint operation output which needs formatting
* @param showTimings true if timings should be in results metadata, false otherwise
* @return final Object with desired JSON format
*/
private static Object formatEndpointOutput(Endpoint.Output output, boolean showTimings) {
if (showTimings) {
return output;
} else {
Map<String, Object> metaNoTimings = Map.of("nb_edges_accessed", output.meta.nbEdgesAccessed);
Map<String, Object> outputNoTimings = Map.of("result", output.result, "meta", metaNoTimings);
return outputNoTimings;
}
}
}

File Metadata

Mime Type
text/x-c
Expires
Jul 4 2025, 6:45 PM (5 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3292532

Event Timeline