From bc1cbb3a7e75c25dfdfe2f8a17c8a60534cdd70d Mon Sep 17 00:00:00 2001 From: Zhangmei Li Date: Wed, 17 May 2017 21:16:40 +0800 Subject: [PATCH] HugeGraph-89: implement rest framework based on jersey Change-Id: I89d033abcec4398ed08e1c8cd261757a4a9b78cd --- .../java/com/baidu/hugegraph/api/API.java | 19 +++ .../com/baidu/hugegraph/api/Application.java | 52 ++++++++ .../hugegraph/api/filter/ExceptionFilter.java | 76 ++++++++++++ .../hugegraph/api/filter/StatusFilter.java | 37 ++++++ .../baidu/hugegraph/api/graph/VertexAPI.java | 92 ++++++++++++++ .../baidu/hugegraph/core/GraphManager.java | 115 ++++++++++++++++++ .../hugegraph/serializer/JsonSerializer.java | 41 +++++++ .../hugegraph/serializer/Serializer.java | 12 ++ .../baidu/hugegraph/server/HugeServer.java | 63 ++++++++++ 9 files changed, 507 insertions(+) create mode 100644 hugegraph-api/src/main/java/com/baidu/hugegraph/api/API.java create mode 100644 hugegraph-api/src/main/java/com/baidu/hugegraph/api/Application.java create mode 100644 hugegraph-api/src/main/java/com/baidu/hugegraph/api/filter/ExceptionFilter.java create mode 100644 hugegraph-api/src/main/java/com/baidu/hugegraph/api/filter/StatusFilter.java create mode 100644 hugegraph-api/src/main/java/com/baidu/hugegraph/api/graph/VertexAPI.java create mode 100644 hugegraph-api/src/main/java/com/baidu/hugegraph/core/GraphManager.java create mode 100644 hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/JsonSerializer.java create mode 100644 hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/Serializer.java create mode 100644 hugegraph-api/src/main/java/com/baidu/hugegraph/server/HugeServer.java diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/API.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/API.java new file mode 100644 index 000000000..96b355db8 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/API.java @@ -0,0 +1,19 @@ +package com.baidu.hugegraph.api; + +import javax.ws.rs.NotFoundException; + +import org.apache.tinkerpop.gremlin.structure.Graph; + +import com.baidu.hugegraph.core.GraphManager; + +public class API { + + public static Graph graph(GraphManager manager, String graph) { + Graph g = manager.graph(graph); + if (g == null) { + String msg = String.format( "Not found graph '%s'", graph); + throw new NotFoundException(msg); + } + return g; + } +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/Application.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/Application.java new file mode 100644 index 000000000..bc405c164 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/Application.java @@ -0,0 +1,52 @@ +package com.baidu.hugegraph.api; + +import java.util.HashMap; +import java.util.Map; + +import javax.ws.rs.ApplicationPath; + +import org.glassfish.hk2.api.Factory; +import org.glassfish.hk2.utilities.binding.AbstractBinder; +import org.glassfish.jersey.process.internal.RequestScoped; +import org.glassfish.jersey.server.ResourceConfig; + +import com.baidu.hugegraph.core.GraphManager; + +@ApplicationPath("/") +public class Application extends ResourceConfig { + + public Application() { + packages("com.baidu.hugegraph.api"); + + // TODO: read from conf + Map graphConfs = new HashMap<>(); + graphConfs.put("hugegraph", + "../hugegraph-dist/src/assembly/static/conf/hugegraph.properties"); + register(new GraphManagerFactory(graphConfs)); + } + + static class GraphManagerFactory extends AbstractBinder + implements Factory { + + private GraphManager manager = null; + + public GraphManagerFactory(final Map graphConfs) { + this.manager = new GraphManager(graphConfs); + } + + @Override + protected void configure() { + bindFactory(this).to(GraphManager.class).in(RequestScoped.class); + } + + @Override + public GraphManager provide() { + return this.manager; + } + + @Override + public void dispose(GraphManager manager) { + // pass + } + } +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/filter/ExceptionFilter.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/filter/ExceptionFilter.java new file mode 100644 index 000000000..a3830492f --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/filter/ExceptionFilter.java @@ -0,0 +1,76 @@ +package com.baidu.hugegraph.api.filter; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +import com.baidu.hugegraph.HugeException; + +public class ExceptionFilter { + + @Provider + public static class HugeExceptionMapper + implements ExceptionMapper { + + @Override + public Response toResponse(HugeException exception) { + return Response.status(400) + .type(MediaType.APPLICATION_JSON) + .entity(formatException(exception)) + .build(); + } + } + + @Provider + public static class IllegalArgumentExceptionMapper + implements ExceptionMapper { + + @Override + public Response toResponse(IllegalArgumentException exception) { + return Response.status(400) + .type(MediaType.APPLICATION_JSON) + .entity(formatException(exception)) + .build(); + } + } + + @Provider + public static class NotFoundExceptionExceptionMapper + implements ExceptionMapper { + + @Override + public Response toResponse(NotFoundException exception) { + return Response.status(404) + .type(MediaType.APPLICATION_JSON) + .entity(formatException(exception)) + .build(); + } + } + + @Provider + public static class UnknownExceptionMapper + implements ExceptionMapper { + + @Override + public Response toResponse(Exception exception) { + return Response.status(500) + .type(MediaType.APPLICATION_JSON) + .entity(formatException(exception)) + .build(); + } + } + + public static String formatException(Exception exception) { + JsonObject json = Json.createObjectBuilder() + .add("exception", exception.getClass().toString()) + .add("message", exception.getMessage()) + .add("cause", (exception.getCause() != null + ? exception.getCause().toString() : "")) + .build(); + return json.toString(); + } +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/filter/StatusFilter.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/filter/StatusFilter.java new file mode 100644 index 000000000..65d5f1f7b --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/filter/StatusFilter.java @@ -0,0 +1,37 @@ +package com.baidu.hugegraph.api.filter; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.ws.rs.NameBinding; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.ext.Provider; + +@Provider +public class StatusFilter implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, + ContainerResponseContext responseContext) throws IOException { + if (responseContext.getStatus() == 200) { + for (Annotation annotation : responseContext.getEntityAnnotations()) { + if (annotation instanceof Status) { + responseContext.setStatus(((Status) annotation).value()); + break; + } + } + } + } + + @NameBinding + @Retention(RetentionPolicy.RUNTIME) + public @interface Status { + final int CREATED = 201; + + int value(); + } +} \ No newline at end of file diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/graph/VertexAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/graph/VertexAPI.java new file mode 100644 index 000000000..6d8cb56a4 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/graph/VertexAPI.java @@ -0,0 +1,92 @@ +package com.baidu.hugegraph.api.graph; + +import java.util.List; + +import javax.inject.Singleton; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +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 javax.ws.rs.core.MediaType; + +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.T; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.baidu.hugegraph.api.API; +import com.baidu.hugegraph.api.filter.StatusFilter.Status; +import com.baidu.hugegraph.core.GraphManager; +import com.baidu.hugegraph.server.HugeServer; +import com.google.common.collect.ImmutableList; + +@Path("graphs/{graph}/graph/vertices") +@Singleton +public class VertexAPI extends API { + + private static final Logger logger = LoggerFactory.getLogger(HugeServer.class); + + @POST + @Status(Status.CREATED) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public String create(@Context GraphManager manager, + @PathParam("graph") String graph, + String keyValues) { + logger.debug("Graph [{}] create vertex: {}", graph, keyValues); + + Graph g = graph(manager, graph); + + // TODO: improve keyValues parse + Object[] props = keyValues.split(","); + if (props[0].equals("T.label")) { + props = ImmutableList.copyOf(props).toArray(); + props[0] = T.label; + } + + return manager.serializer(g).writeVertex(g.addVertex(props)); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public String list(@Context GraphManager manager, + @PathParam("graph") String graph, + @DefaultValue("100") @QueryParam("limit") long limit) { + logger.debug("Graph [{}] get vertices", graph); + + Graph g = graph(manager, graph); + List rs = g.traversal().V().limit(limit).toList(); + return manager.serializer(g).writeVertices(rs); + } + + @GET + @Path("{id}") + @Produces(MediaType.APPLICATION_JSON) + public String get(@Context GraphManager manager, + @PathParam("graph") String graph, + @PathParam("id") String id) { + logger.debug("Graph [{}] get vertex by id '{}'", graph, id); + + Graph g = graph(manager, graph); + return manager.serializer(g).writeVertex(g.vertices(id).next()); + } + + @DELETE + @Consumes(MediaType.APPLICATION_JSON) + public void delete(@Context GraphManager manager, + @PathParam("graph") String graph, + @PathParam("id") String id) { + logger.debug("Graph [{}] remove vertex by id '{}'", graph, id); + + Graph g = graph(manager, graph); + // TODO: add removeVertex(id) to improve + g.vertices(id).next().remove(); + } +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/core/GraphManager.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/core/GraphManager.java new file mode 100644 index 000000000..f650f5ecb --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/core/GraphManager.java @@ -0,0 +1,115 @@ +package com.baidu.hugegraph.core; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Transaction; +import org.apache.tinkerpop.gremlin.structure.io.IoCore; +import org.apache.tinkerpop.gremlin.structure.util.GraphFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.baidu.hugegraph.serializer.JsonSerializer; +import com.baidu.hugegraph.serializer.Serializer; +import com.baidu.hugegraph.server.HugeServer; + +public final class GraphManager { + + private static final Logger logger = LoggerFactory.getLogger(HugeServer.class); + + private final Map graphs; + private final Map traversalSources; + + public GraphManager(final Map graphConfs) { + this.graphs = new ConcurrentHashMap<>(); + this.traversalSources = new ConcurrentHashMap<>(); + + loadGraphs(graphConfs); + } + + protected void loadGraphs(final Map graphConfs) { + graphConfs.entrySet().forEach(conf -> { + try { + final Graph newGraph = GraphFactory.open(conf.getValue()); + this.graphs.put(conf.getKey(), newGraph); + logger.info("Graph '{}' was successfully configured via '{}'.", + conf.getKey(), conf.getValue()); + } catch (RuntimeException e) { + logger.warn("Graph '{}': '{}' could not be instantiated", + conf.getKey(), conf.getValue(), e); + } + }); + } + + public Map graphs() { + return this.graphs; + } + + public Graph graph(String name) { + return this.graphs.get(name); + } + + public Serializer serializer(Graph g) { + // TODO: cache Serializer + return new JsonSerializer(g.io(IoCore.graphson()).writer().create()); + } + + public Map traversalSources() { + return this.traversalSources; + } + + public void rollbackAll() { + this.graphs.entrySet().forEach(e -> { + final Graph graph = e.getValue(); + if (graph.features().graph().supportsTransactions() + && graph.tx().isOpen()) { + graph.tx().rollback(); + } + }); + } + + public void rollback(final Set graphSourceNamesToCloseTxOn) { + closeTx(graphSourceNamesToCloseTxOn, Transaction.Status.ROLLBACK); + } + + public void commitAll() { + this.graphs.entrySet().forEach(e -> { + final Graph graph = e.getValue(); + if (graph.features().graph().supportsTransactions() + && graph.tx().isOpen()) { + graph.tx().commit(); + } + }); + } + + public void commit(final Set graphSourceNamesToCloseTxOn) { + closeTx(graphSourceNamesToCloseTxOn, Transaction.Status.COMMIT); + } + + private void closeTx(final Set graphSourceNamesToCloseTxOn, + final Transaction.Status tx) { + final Set graphsToCloseTxOn = new HashSet<>(); + + graphSourceNamesToCloseTxOn.forEach(r -> { + if (this.graphs.containsKey(r)) + graphsToCloseTxOn.add(this.graphs.get(r)); + else + graphsToCloseTxOn.add(this.traversalSources.get(r).getGraph()); + }); + + graphsToCloseTxOn.forEach(graph -> { + if (graph.features().graph().supportsTransactions() + && graph.tx().isOpen()) { + if (tx == Transaction.Status.COMMIT) { + graph.tx().commit(); + } else { + graph.tx().rollback(); + } + } + }); + } +} 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 new file mode 100644 index 000000000..32dbaf047 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/JsonSerializer.java @@ -0,0 +1,41 @@ +package com.baidu.hugegraph.serializer; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONWriter; + +import com.baidu.hugegraph.HugeException; + +public class JsonSerializer implements Serializer { + + private GraphSONWriter writer; + + public JsonSerializer(GraphSONWriter writer) { + this.writer = writer; + } + + @Override + public String writeVertex(Vertex v) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + this.writer.writeVertex(out, v); + } catch (IOException e) { + throw new HugeException("Failed to serialize vertex", e); + } + return out.toString(); + } + + @Override + public String writeVertices(List vertices) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + this.writer.writeVertices(out, vertices.iterator()); + } catch (IOException e) { + throw new HugeException("Failed to serialize vertices", e); + } + return out.toString(); + } +} 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 new file mode 100644 index 000000000..3021e3edb --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/serializer/Serializer.java @@ -0,0 +1,12 @@ +package com.baidu.hugegraph.serializer; + +import java.util.List; + +import org.apache.tinkerpop.gremlin.structure.Vertex; + +public interface Serializer { + + public String writeVertex(Vertex v); + + public String writeVertices(List vertices); +} diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/server/HugeServer.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/server/HugeServer.java new file mode 100644 index 000000000..a25064631 --- /dev/null +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/server/HugeServer.java @@ -0,0 +1,63 @@ +package com.baidu.hugegraph.server; + +import java.io.IOException; +import java.net.URI; + +import javax.ws.rs.core.UriBuilder; + +import org.glassfish.grizzly.http.server.HttpServer; +import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; +import org.glassfish.jersey.server.ResourceConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.baidu.hugegraph.api.Application; +import com.google.common.base.Preconditions; + +public class HugeServer { + + private static final Logger logger = LoggerFactory.getLogger(HugeServer.class); + + private HttpServer httpServer = null; + + public void start() throws IllegalArgumentException, IOException { + // TODO: read from conf + URI uri = UriBuilder.fromUri("http://127.0.0.1").port(8080).build(); + + ResourceConfig rc = new Application(); + + this.httpServer = GrizzlyHttpServerFactory.createHttpServer(uri, rc); + this.httpServer.start(); + } + + public void stop() { + Preconditions.checkNotNull(this.httpServer); + this.httpServer.stop(); + } + + public static void initEnv(String[] args) { + // pass + } + + public static HugeServer start(String[] args) { + logger.info("HugeServer starting..."); + // HugeServer.loadConf(args); + HugeServer.initEnv(args); + + HugeServer server = new HugeServer(); + try { + server.start(); + logger.info("HugeServer started"); + } catch (IOException e) { + logger.error("Failed to start HugeServer", e); + } + + return server; + } + + public static void main(String[] args) throws Exception { + HugeServer.start(args); + Thread.currentThread().join(); + logger.info("HugeServer stopped"); + } +}