HugeGraph-89: implement rest framework based on jersey

Change-Id: I89d033abcec4398ed08e1c8cd261757a4a9b78cd
This commit is contained in:
Zhangmei Li 2017-05-17 21:16:40 +08:00 committed by liningrui
parent 717cbb9597
commit bc1cbb3a7e
9 changed files with 507 additions and 0 deletions

View File

@ -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;
}
}

View File

@ -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<String, String> 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<GraphManager> {
private GraphManager manager = null;
public GraphManagerFactory(final Map<String, String> 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
}
}
}

View File

@ -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<HugeException> {
@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<IllegalArgumentException> {
@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<NotFoundException> {
@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<Exception> {
@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();
}
}

View File

@ -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();
}
}

View File

@ -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<Vertex> 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();
}
}

View File

@ -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<String, Graph> graphs;
private final Map<String, TraversalSource> traversalSources;
public GraphManager(final Map<String, String> graphConfs) {
this.graphs = new ConcurrentHashMap<>();
this.traversalSources = new ConcurrentHashMap<>();
loadGraphs(graphConfs);
}
protected void loadGraphs(final Map<String, String> 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<String, Graph> 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<String, TraversalSource> 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<String> 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<String> graphSourceNamesToCloseTxOn) {
closeTx(graphSourceNamesToCloseTxOn, Transaction.Status.COMMIT);
}
private void closeTx(final Set<String> graphSourceNamesToCloseTxOn,
final Transaction.Status tx) {
final Set<Graph> 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();
}
}
});
}
}

View File

@ -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<Vertex> 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();
}
}

View File

@ -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<Vertex> vertices);
}

View File

@ -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");
}
}