add graphql route in s2http

Project: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/commit/dcaa1f34
Tree: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/tree/dcaa1f34
Diff: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/diff/dcaa1f34

Branch: refs/heads/master
Commit: dcaa1f34c4dd9bba863d5e532b9b0893e6b9a287
Parents: f41ab56
Author: daewon <dae...@apache.org>
Authored: Fri Nov 30 16:46:03 2018 +0900
Committer: daewon <dae...@apache.org>
Committed: Fri Nov 30 16:46:03 2018 +0900

----------------------------------------------------------------------
 s2graphql/build.sbt                             |  12 +-
 s2graphql/src/main/resources/application.conf   |  11 +-
 s2graphql/src/main/resources/assets/.gitignore  |   1 -
 .../src/main/resources/assets/graphiql.html     | 151 ++++++++++++++++++
 .../apache/s2graph/graphql/GraphQLServer.scala  |  81 ++++------
 .../org/apache/s2graph/graphql/HttpServer.scala | 154 -------------------
 s2graphql/src/test/resources/application.conf   |  11 +-
 .../apache/s2graph/http/PlayJsonSupport.scala   |   4 +-
 .../s2graph/http/S2GraphMutateRoute.scala       |   1 -
 .../apache/s2graph/http/S2GraphQLRoute.scala    | 105 +++++++++++++
 .../s2graph/http/SangriaGraphQLSupport.scala    |  38 +++++
 .../scala/org/apache/s2graph/http/Server.scala  |  14 +-
 12 files changed, 351 insertions(+), 232 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/dcaa1f34/s2graphql/build.sbt
----------------------------------------------------------------------
diff --git a/s2graphql/build.sbt b/s2graphql/build.sbt
index bc71f98..dbf38c3 100644
--- a/s2graphql/build.sbt
+++ b/s2graphql/build.sbt
@@ -29,15 +29,11 @@ libraryDependencies ++= Seq(
   "org.scala-lang" % "scala-compiler" % scalaVersion.value,
   "org.scala-lang" % "scala-reflect" % scalaVersion.value,
 
-  "org.sangria-graphql" %% "sangria" % "1.4.0",
-  "org.sangria-graphql" %% "sangria-spray-json" % "1.0.0",
-  "org.sangria-graphql" %% "sangria-play-json" % "1.0.1" % Test,
+  "org.sangria-graphql" %% "sangria" % "1.4.2",
+  "org.sangria-graphql" %% "sangria-spray-json" % "1.0.1",
+  "org.sangria-graphql" %% "sangria-play-json" % "1.0.5" % Test,
 
-  "com.typesafe.akka" %% "akka-http" % "10.0.10",
-  "com.typesafe.akka" %% "akka-http-spray-json" % "10.0.10",
-  "com.typesafe.akka" %% "akka-slf4j" % "2.4.6",
-
-  "org.scalatest" %% "scalatest" % "3.0.4" % Test
+  "org.scalatest" %% "scalatest" % "3.0.5" % Test
 )
 
 Revolver.settings

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/dcaa1f34/s2graphql/src/main/resources/application.conf
----------------------------------------------------------------------
diff --git a/s2graphql/src/main/resources/application.conf 
b/s2graphql/src/main/resources/application.conf
index aac21d1..b956031 100644
--- a/s2graphql/src/main/resources/application.conf
+++ b/s2graphql/src/main/resources/application.conf
@@ -16,11 +16,12 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-akka {
-  loggers = ["akka.event.slf4j.Slf4jLogger"]
-  event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
-  loglevel = "INFO"
-}
+
+//akka {
+//  loggers = ["akka.event.slf4j.Slf4jLogger"]
+//  event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
+//  loglevel = "INFO"
+//}
 
 //db.default.url="jdbc:h2:file:./var/metastore;MODE=MYSQL",
 //db.default.password = sa

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/dcaa1f34/s2graphql/src/main/resources/assets/.gitignore
----------------------------------------------------------------------
diff --git a/s2graphql/src/main/resources/assets/.gitignore 
b/s2graphql/src/main/resources/assets/.gitignore
deleted file mode 100644
index 8b13789..0000000
--- a/s2graphql/src/main/resources/assets/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/dcaa1f34/s2graphql/src/main/resources/assets/graphiql.html
----------------------------------------------------------------------
diff --git a/s2graphql/src/main/resources/assets/graphiql.html 
b/s2graphql/src/main/resources/assets/graphiql.html
new file mode 100644
index 0000000..3fe0083
--- /dev/null
+++ b/s2graphql/src/main/resources/assets/graphiql.html
@@ -0,0 +1,151 @@
+<!--
+ * LICENSE AGREEMENT For GraphiQL software
+ *
+ * Facebook, Inc. (“Facebook”) owns all right, title and interest, 
including all
+ * intellectual property and other proprietary rights, in and to the GraphiQL
+ * software. Subject to your compliance with these terms, you are hereby 
granted a
+ * non-exclusive, worldwide, royalty-free copyright license to (1) use and 
copy the
+ * GraphiQL software; and (2) reproduce and distribute the GraphiQL software as
+ * part of your own software (“Your Software”). Facebook reserves all 
rights not
+ * expressly granted to you in this license agreement.
+ *
+ * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY 
EXPRESS OR
+ * IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. IN NO
+ * EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICES, DIRECTORS OR EMPLOYEES BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
OUT OF
+ * THE USE OF THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You will include in Your Software (e.g., in the file(s), documentation or 
other
+ * materials accompanying your software): (1) the disclaimer set forth above; 
(2)
+ * this sentence; and (3) the following copyright notice:
+ *
+ * Copyright (c) 2015, Facebook, Inc. All rights reserved.
+-->
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      body {
+        height: 100%;
+        margin: 0;
+        width: 100%;
+        overflow: hidden;
+      }
+
+      #graphiql {
+        height: 100vh;
+      }
+    </style>
+
+    <link rel="stylesheet" 
href="//cdn.jsdelivr.net/npm/graphiql@0.11.11/graphiql.css" />
+    <script 
src="//cdn.jsdelivr.net/es6-promise/4.0.5/es6-promise.auto.min.js"></script>
+    <script src="//cdn.jsdelivr.net/fetch/0.9.0/fetch.min.js"></script>
+    <script src="//cdn.jsdelivr.net/react/15.4.2/react.min.js"></script>
+    <script src="//cdn.jsdelivr.net/react/15.4.2/react-dom.min.js"></script>
+    <script 
src="//cdn.jsdelivr.net/npm/graphiql@0.11.11/graphiql.min.js"></script>
+  </head>
+  <body>
+    <div id="graphiql">Loading...</div>
+
+    <script>
+
+      /**
+       * This GraphiQL example illustrates how to use some of GraphiQL's props
+       * in order to enable reading and updating the URL parameters, making
+       * link sharing of queries a little bit easier.
+       *
+       * This is only one example of this kind of feature, GraphiQL exposes
+       * various React params to enable interesting integrations.
+       */
+
+      // Parse the search string to get url parameters.
+      var search = window.location.search;
+      var parameters = {};
+      search.substr(1).split('&').forEach(function (entry) {
+        var eq = entry.indexOf('=');
+        if (eq >= 0) {
+          parameters[decodeURIComponent(entry.slice(0, eq))] =
+            decodeURIComponent(entry.slice(eq + 1));
+        }
+      });
+
+      // if variables was provided, try to format it.
+      if (parameters.variables) {
+        try {
+          parameters.variables =
+            JSON.stringify(JSON.parse(parameters.variables), null, 2);
+        } catch (e) {
+          // Do nothing, we want to display the invalid JSON as a string, 
rather
+          // than present an error.
+        }
+      }
+
+      // When the query and variables string is edited, update the URL bar so
+      // that it can be easily shared
+      function onEditQuery(newQuery) {
+        parameters.query = newQuery;
+        updateURL();
+      }
+
+      function onEditVariables(newVariables) {
+        parameters.variables = newVariables;
+        updateURL();
+      }
+
+      function onEditOperationName(newOperationName) {
+        parameters.operationName = newOperationName;
+        updateURL();
+      }
+
+      function updateURL() {
+        var newSearch = '?' + Object.keys(parameters).filter(function (key) {
+          return Boolean(parameters[key]);
+        }).map(function (key) {
+          return encodeURIComponent(key) + '=' +
+            encodeURIComponent(parameters[key]);
+        }).join('&');
+        history.replaceState(null, null, newSearch);
+      }
+
+      // Defines a GraphQL fetcher using the fetch API.
+      function graphQLFetcher(graphQLParams) {
+        return fetch('/graphql', {
+          method: 'post',
+          headers: {
+            'Accept': 'application/json',
+            'Content-Type': 'application/json',
+          },
+          body: JSON.stringify(graphQLParams),
+          credentials: 'include',
+        }).then(function (response) {
+          return response.text();
+        }).then(function (responseBody) {
+          try {
+            return JSON.parse(responseBody);
+          } catch (error) {
+            return responseBody;
+          }
+        });
+      }
+
+      // Render <GraphiQL /> into the body.
+      ReactDOM.render(
+        React.createElement(GraphiQL, {
+          fetcher: graphQLFetcher,
+          query: parameters.query,
+          variables: parameters.variables,
+          operationName: parameters.operationName,
+          onEditQuery: onEditQuery,
+          onEditVariables: onEditVariables,
+          onEditOperationName: onEditOperationName
+        }),
+        document.getElementById('graphiql')
+      );
+    </script>
+  </body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/dcaa1f34/s2graphql/src/main/scala/org/apache/s2graph/graphql/GraphQLServer.scala
----------------------------------------------------------------------
diff --git 
a/s2graphql/src/main/scala/org/apache/s2graph/graphql/GraphQLServer.scala 
b/s2graphql/src/main/scala/org/apache/s2graph/graphql/GraphQLServer.scala
index b650714..a013b2d 100644
--- a/s2graphql/src/main/scala/org/apache/s2graph/graphql/GraphQLServer.scala
+++ b/s2graphql/src/main/scala/org/apache/s2graph/graphql/GraphQLServer.scala
@@ -19,15 +19,9 @@
 
 package org.apache.s2graph.graphql
 
-import java.util.concurrent.Executors
-
-import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
-import akka.http.scaladsl.model.StatusCodes._
-import akka.http.scaladsl.server.Directives._
-import akka.http.scaladsl.server._
 import com.typesafe.config.ConfigFactory
-import org.apache.s2graph.graphql.middleware.{GraphFormatted, Transform}
-import org.apache.s2graph.core.S2Graph
+import org.apache.s2graph.graphql.middleware.{GraphFormatted}
+import org.apache.s2graph.core.{S2GraphLike}
 import org.apache.s2graph.core.utils.SafeUpdateCache
 import org.apache.s2graph.graphql.repository.GraphRepository
 import org.apache.s2graph.graphql.types.SchemaDef
@@ -36,36 +30,42 @@ import sangria.ast.Document
 import sangria.execution._
 import sangria.execution.deferred.DeferredResolver
 import sangria.marshalling.sprayJson._
-import sangria.parser.{QueryParser, SyntaxError}
+import sangria.parser.{SyntaxError}
 import sangria.schema.Schema
 import spray.json._
 
 import scala.collection.JavaConverters._
-import scala.concurrent.{ExecutionContext, Future}
+import scala.concurrent.{ExecutionContext}
 import scala.util.control.NonFatal
-import scala.util.{Failure, Success, Try}
+import scala.util._
 
-class GraphQLServer() {
-  val className = Schema.getClass.getName
-  val logger = LoggerFactory.getLogger(this.getClass)
+object GraphQLServer {
+  def formatError(error: Throwable): JsValue = error match {
+    case syntaxError: SyntaxError ⇒
+      JsObject("errors" → JsArray(
+        JsObject(
+          "message" → JsString(syntaxError.getMessage),
+          "locations" → JsArray(JsObject(
+            "line" → JsNumber(syntaxError.originalError.position.line),
+            "column" → 
JsNumber(syntaxError.originalError.position.column))))))
 
-  // Init s2graph
-  val numOfThread = Runtime.getRuntime.availableProcessors()
-  val threadPool = Executors.newFixedThreadPool(numOfThread * 2)
+    case NonFatal(e) ⇒ formatError(e.toString)
+    case e ⇒ throw e
+  }
 
-  implicit val ec = ExecutionContext.fromExecutor(threadPool)
+  def formatError(message: String): JsObject =
+    JsObject("errors" → JsArray(JsObject("message" → JsString(message))))
+}
 
-  val config = ConfigFactory.load()
-  val s2graph = new S2Graph(config)
-  val schemaCacheTTL = Try(config.getInt("schemaCacheTTL")).getOrElse(3000)
-  val enableMutation = 
Try(config.getBoolean("enableMutation")).getOrElse(false)
+class GraphQLServer(s2graph: S2GraphLike, schemaCacheTTL: Int = 60) {
+  val logger = LoggerFactory.getLogger(this.getClass)
 
   val schemaConfig = ConfigFactory.parseMap(Map(
     SafeUpdateCache.MaxSizeKey -> 1, SafeUpdateCache.TtlKey -> schemaCacheTTL
   ).asJava)
 
   // Manage schema instance lifecycle
-  val schemaCache = new SafeUpdateCache(schemaConfig)
+  val schemaCache = new SafeUpdateCache(schemaConfig)(s2graph.ec)
 
   def updateEdgeFetcher(requestJSON: spray.json.JsValue)(implicit e: 
ExecutionContext): Try[Unit] = {
     val ret = Try {
@@ -79,9 +79,9 @@ class GraphQLServer() {
     ret
   }
 
-  val schemaCacheKey = className + "s2Schema"
+  val schemaCacheKey = Schema.getClass.getName + "s2Schema"
 
-  schemaCache.put(schemaCacheKey, createNewSchema(enableMutation))
+  schemaCache.put(schemaCacheKey, createNewSchema(true))
 
   /**
     * In development mode(schemaCacheTTL = 1),
@@ -101,33 +101,17 @@ class GraphQLServer() {
     newSchema -> s2Repository
   }
 
-  def formatError(error: Throwable): JsValue = error match {
-    case syntaxError: SyntaxError ⇒
-      JsObject("errors" → JsArray(
-        JsObject(
-          "message" → JsString(syntaxError.getMessage),
-          "locations" → JsArray(JsObject(
-            "line" → JsNumber(syntaxError.originalError.position.line),
-            "column" → 
JsNumber(syntaxError.originalError.position.column))))))
-
-    case NonFatal(e) ⇒ formatError(e.toString)
-    case e ⇒ throw e
-  }
-
-  def formatError(message: String): JsObject =
-    JsObject("errors" → JsArray(JsObject("message" → JsString(message))))
-
   def onEvictSchema(o: AnyRef): Unit = {
     logger.info("Schema Evicted")
   }
 
   val TransformMiddleWare = 
List(org.apache.s2graph.graphql.middleware.Transform())
 
-  def executeGraphQLQuery(query: Document, op: Option[String], vars: 
JsObject)(implicit e: ExecutionContext) = {
+  def executeQuery(query: Document, op: Option[String], vars: 
JsObject)(implicit e: ExecutionContext) = {
     import GraphRepository._
 
     val (schemaDef, s2Repository) =
-      schemaCache.withCache(schemaCacheKey, broadcast = false, onEvict = 
onEvictSchema)(createNewSchema(enableMutation))
+      schemaCache.withCache(schemaCacheKey, broadcast = false, onEvict = 
onEvictSchema)(createNewSchema(true))
 
     val resolver: DeferredResolver[GraphRepository] = 
DeferredResolver.fetchers(vertexFetcher, edgeFetcher)
 
@@ -142,15 +126,8 @@ class GraphQLServer() {
       operationName = op,
       deferredResolver = resolver,
       middleware = middleWares
-    ).map((res: spray.json.JsValue) => OK -> res)
-      .recover {
-        case error: QueryAnalysisError =>
-          logger.error("Error on execute", error)
-          BadRequest -> error.resolveError
-        case error: ErrorWithResolver =>
-          logger.error("Error on execute", error)
-          InternalServerError -> error.resolveError
-      }
+    )
   }
+
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/dcaa1f34/s2graphql/src/main/scala/org/apache/s2graph/graphql/HttpServer.scala
----------------------------------------------------------------------
diff --git 
a/s2graphql/src/main/scala/org/apache/s2graph/graphql/HttpServer.scala 
b/s2graphql/src/main/scala/org/apache/s2graph/graphql/HttpServer.scala
deleted file mode 100644
index 8b89c73..0000000
--- a/s2graphql/src/main/scala/org/apache/s2graph/graphql/HttpServer.scala
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * 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 org.apache.s2graph.graphql
-
-import java.nio.charset.Charset
-
-import akka.actor.ActorSystem
-import akka.http.scaladsl.Http
-import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
-import akka.http.scaladsl.model._
-import akka.http.scaladsl.server.Directives._
-import akka.http.scaladsl.server.{Route, StandardRoute}
-import akka.stream.ActorMaterializer
-import org.slf4j.LoggerFactory
-import sangria.parser.QueryParser
-import spray.json._
-
-import scala.concurrent.Await
-import scala.language.postfixOps
-import scala.util._
-import akka.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller, 
ToResponseMarshallable}
-import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, Unmarshaller}
-import akka.util.ByteString
-import sangria.ast.Document
-import sangria.renderer.{QueryRenderer, QueryRendererConfig}
-
-import scala.collection.immutable.Seq
-
-object Server extends App {
-  val logger = LoggerFactory.getLogger(this.getClass)
-
-  implicit val system = ActorSystem("s2graphql-server")
-  implicit val materializer = ActorMaterializer()
-
-  import system.dispatcher
-  import scala.concurrent.duration._
-
-  import spray.json.DefaultJsonProtocol._
-
-  val graphQLServer = new GraphQLServer()
-
-  val route: Route =
-    get {
-      getFromResource("assets/graphiql.html")
-    } ~ (post & path("updateEdgeFetcher")) {
-      entity(as[JsValue]) { body =>
-        graphQLServer.updateEdgeFetcher(body) match {
-          case Success(_) => complete(StatusCodes.OK -> JsString("Update 
fetcher finished"))
-          case Failure(e) =>
-            logger.error("Error on execute", e)
-            complete(StatusCodes.InternalServerError -> 
spray.json.JsObject("message" -> JsString(e.toString)))
-        }
-      }
-    } ~ (post & path("graphql")) {
-      parameters('operationName.?, 'variables.?) { (operationNameParam, 
variablesParam) =>
-        entity(as[Document]) { document ⇒
-          variablesParam.map(parseJson) match {
-            case None ⇒ complete(graphQLServer.executeGraphQLQuery(document, 
operationNameParam, JsObject()))
-            case Some(Right(js)) ⇒ 
complete(graphQLServer.executeGraphQLQuery(document, operationNameParam, 
js.asJsObject))
-            case Some(Left(e)) ⇒
-              logger.error("Error on execute", e)
-              complete(StatusCodes.BadRequest -> graphQLServer.formatError(e))
-          }
-        } ~ entity(as[JsValue]) { body ⇒
-          val fields = body.asJsObject.fields
-
-          val query = fields.get("query").map(js => js.convertTo[String])
-          val operationName = fields.get("operationName").filterNot(_ == 
JsNull).map(_.convertTo[String])
-          val variables = fields.get("variables").filterNot(_ == JsNull)
-
-          query.map(QueryParser.parse(_)) match {
-            case None ⇒ complete(StatusCodes.BadRequest -> 
graphQLServer.formatError("No query to execute"))
-            case Some(Failure(error)) ⇒
-              logger.error("Error on execute", error)
-              complete(StatusCodes.BadRequest -> 
graphQLServer.formatError(error))
-            case Some(Success(document)) => variables match {
-              case Some(js) ⇒ 
complete(graphQLServer.executeGraphQLQuery(document, operationName, 
js.asJsObject))
-              case None ⇒ 
complete(graphQLServer.executeGraphQLQuery(document, operationName, JsObject()))
-            }
-          }
-        }
-      }
-    }
-
-  val port = sys.props.get("http.port").fold(8000)(_.toInt)
-
-  logger.info(s"Starting GraphQL server... $port")
-
-  Http().bindAndHandle(route, "0.0.0.0", port).foreach { binding =>
-    logger.info(s"GraphQL server ready for connect")
-  }
-
-  def shutdown(): Unit = {
-    logger.info("Terminating...")
-
-    system.terminate()
-    Await.result(system.whenTerminated, 30 seconds)
-
-    logger.info("Terminated.")
-  }
-
-  // Unmarshaller
-
-  def unmarshallerContentTypes: Seq[ContentTypeRange] = 
mediaTypes.map(ContentTypeRange.apply)
-
-  def mediaTypes: Seq[MediaType.WithFixedCharset] =
-    Seq(MediaType.applicationWithFixedCharset("graphql", HttpCharsets.`UTF-8`, 
"graphql"))
-
-  implicit def documentMarshaller(implicit config: QueryRendererConfig = 
QueryRenderer.Compact): ToEntityMarshaller[Document] = {
-    Marshaller.oneOf(mediaTypes: _*) {
-      mediaType ⇒
-        Marshaller.withFixedContentType(ContentType(mediaType)) {
-          json ⇒ HttpEntity(mediaType, QueryRenderer.render(json, config))
-        }
-    }
-  }
-
-  implicit val documentUnmarshaller: FromEntityUnmarshaller[Document] = {
-    Unmarshaller.byteStringUnmarshaller
-      .forContentTypes(unmarshallerContentTypes: _*)
-      .map {
-        case ByteString.empty ⇒ throw Unmarshaller.NoContentException
-        case data ⇒
-          import sangria.parser.DeliveryScheme.Throw
-          QueryParser.parse(data.decodeString(Charset.forName("UTF-8")))
-      }
-  }
-
-  def parseJson(jsStr: String): Either[Throwable, JsValue] = {
-    val parsed = Try(jsStr.parseJson)
-    parsed match {
-      case Success(js) => Right(js)
-      case Failure(e) => Left(e)
-    }
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/dcaa1f34/s2graphql/src/test/resources/application.conf
----------------------------------------------------------------------
diff --git a/s2graphql/src/test/resources/application.conf 
b/s2graphql/src/test/resources/application.conf
index 74821e4..31d9cbd 100644
--- a/s2graphql/src/test/resources/application.conf
+++ b/s2graphql/src/test/resources/application.conf
@@ -16,11 +16,12 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-akka {
-  loggers = ["akka.event.slf4j.Slf4jLogger"]
-  event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
-  loglevel = "INFO"
-}
+
+//akka {
+//  loggers = ["akka.event.slf4j.Slf4jLogger"]
+//  event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
+//  loglevel = "INFO"
+//}
 
 //db.default.url="jdbc:h2:file:./var/metastore;MODE=MYSQL",
 //db.default.password = sa

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/dcaa1f34/s2http/src/main/scala/org/apache/s2graph/http/PlayJsonSupport.scala
----------------------------------------------------------------------
diff --git 
a/s2http/src/main/scala/org/apache/s2graph/http/PlayJsonSupport.scala 
b/s2http/src/main/scala/org/apache/s2graph/http/PlayJsonSupport.scala
index 244e588..b41ebd8 100644
--- a/s2http/src/main/scala/org/apache/s2graph/http/PlayJsonSupport.scala
+++ b/s2http/src/main/scala/org/apache/s2graph/http/PlayJsonSupport.scala
@@ -10,10 +10,10 @@ import play.api.libs.json._
 
 trait PlayJsonSupport {
 
-  val mediaTypes: Seq[MediaType.WithFixedCharset] =
+  private val mediaTypes: Seq[MediaType.WithFixedCharset] =
     Seq(MediaType.applicationWithFixedCharset("json", HttpCharsets.`UTF-8`, 
"js"))
 
-  val unmarshallerContentTypes: Seq[ContentTypeRange] = 
mediaTypes.map(ContentTypeRange.apply)
+  private val unmarshallerContentTypes: Seq[ContentTypeRange] = 
mediaTypes.map(ContentTypeRange.apply)
 
   implicit val playJsonMarshaller: ToEntityMarshaller[JsValue] = {
     Marshaller.oneOf(mediaTypes: _*) { mediaType =>

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/dcaa1f34/s2http/src/main/scala/org/apache/s2graph/http/S2GraphMutateRoute.scala
----------------------------------------------------------------------
diff --git 
a/s2http/src/main/scala/org/apache/s2graph/http/S2GraphMutateRoute.scala 
b/s2http/src/main/scala/org/apache/s2graph/http/S2GraphMutateRoute.scala
index a65db5a..e7d1b88 100644
--- a/s2http/src/main/scala/org/apache/s2graph/http/S2GraphMutateRoute.scala
+++ b/s2http/src/main/scala/org/apache/s2graph/http/S2GraphMutateRoute.scala
@@ -19,7 +19,6 @@ trait S2GraphMutateRoute extends PlayJsonSupport {
 
   lazy val parser = new RequestParser(s2graph)
 
-  //  lazy val requestParser = new RequestParser(s2graph)
   lazy val exceptionHandler = ExceptionHandler {
     case ex: JsonParseException => complete(StatusCodes.BadRequest -> 
ex.getMessage)
     case ex: java.lang.IllegalArgumentException => 
complete(StatusCodes.BadRequest -> ex.getMessage)

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/dcaa1f34/s2http/src/main/scala/org/apache/s2graph/http/S2GraphQLRoute.scala
----------------------------------------------------------------------
diff --git a/s2http/src/main/scala/org/apache/s2graph/http/S2GraphQLRoute.scala 
b/s2http/src/main/scala/org/apache/s2graph/http/S2GraphQLRoute.scala
new file mode 100644
index 0000000..bf7c92e
--- /dev/null
+++ b/s2http/src/main/scala/org/apache/s2graph/http/S2GraphQLRoute.scala
@@ -0,0 +1,105 @@
+package org.apache.s2graph.http
+
+import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
+import akka.http.scaladsl.model._
+import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.server._
+import org.apache.s2graph.core.S2Graph
+import org.apache.s2graph.graphql.GraphQLServer
+import org.slf4j.LoggerFactory
+import sangria.ast.Document
+import sangria.execution.{ErrorWithResolver, QueryAnalysisError}
+import sangria.parser.QueryParser
+import spray.json.{JsNull, JsObject, JsString, JsValue}
+
+import scala.util.{Failure, Left, Right, Success, Try}
+
+object S2GraphQLRoute {
+  def parseJson(jsStr: String): Either[Throwable, JsValue] = {
+    import spray.json._
+    val parsed = Try(jsStr.parseJson)
+
+    parsed match {
+      case Success(js) => Right(js)
+      case Failure(e) => Left(e)
+    }
+  }
+}
+
+trait S2GraphQLRoute extends SprayJsonSupport with SangriaGraphQLSupport {
+
+  import S2GraphQLRoute._
+  import spray.json.DefaultJsonProtocol._
+  import sangria.marshalling.sprayJson._
+
+  val s2graph: S2Graph
+  val logger = LoggerFactory.getLogger(this.getClass)
+
+  lazy val graphQLServer = new GraphQLServer(s2graph)
+
+  private val exceptionHandler = ExceptionHandler {
+    case error: QueryAnalysisError =>
+      logger.error("Error on execute", error)
+      complete(StatusCodes.BadRequest -> error.resolveError)
+    case error: ErrorWithResolver =>
+      logger.error("Error on execute", error)
+      complete(StatusCodes.InternalServerError -> error.resolveError)
+  }
+
+  lazy val updateEdgeFetcher = path("updateEdgeFetcher") {
+    entity(as[spray.json.JsValue]) { body =>
+      graphQLServer.updateEdgeFetcher(body)(s2graph.ec) match {
+        case Success(_) => complete(StatusCodes.OK -> JsString("Update fetcher 
finished"))
+        case Failure(e) =>
+          logger.error("Error on execute", e)
+          complete(StatusCodes.InternalServerError -> 
spray.json.JsObject("message" -> JsString(e.toString)))
+      }
+    }
+  }
+
+  lazy val graphql = parameters('operationName.?, 'variables.?) { 
(operationNameParam, variablesParam) =>
+    implicit val ec = s2graph.ec
+
+    entity(as[Document]) { document ⇒
+      variablesParam.map(parseJson) match {
+        case None ⇒ complete(graphQLServer.executeQuery(document, 
operationNameParam, JsObject()))
+        case Some(Right(js)) ⇒ complete(graphQLServer.executeQuery(document, 
operationNameParam, js.asJsObject))
+        case Some(Left(e)) ⇒
+          logger.error("Error on execute", e)
+          complete(StatusCodes.BadRequest -> GraphQLServer.formatError(e))
+      }
+    } ~ entity(as[spray.json.JsValue]) { body ⇒
+      val fields = body.asJsObject.fields
+
+      val query = fields.get("query").map(js => js.convertTo[String])
+      val operationName = fields.get("operationName").filterNot(_ == 
JsNull).map(_.convertTo[String])
+      val variables = fields.get("variables").filterNot(_ == JsNull)
+
+      query.map(QueryParser.parse(_)) match {
+        case None ⇒ complete(StatusCodes.BadRequest -> 
GraphQLServer.formatError("No query to execute"))
+        case Some(Failure(error)) ⇒
+          logger.error("Error on execute", error)
+          complete(StatusCodes.BadRequest -> GraphQLServer.formatError(error))
+        case Some(Success(document)) => variables match {
+          case Some(js) ⇒ complete(graphQLServer.executeQuery(document, 
operationName, js.asJsObject))
+          case None ⇒ complete(graphQLServer.executeQuery(document, 
operationName, JsObject()))
+        }
+      }
+    }
+  }
+
+  // expose routes
+  lazy val graphqlRoute: Route =
+    get {
+      getFromResource("assets/graphiql.html")
+    } ~
+      post {
+        handleExceptions(exceptionHandler) {
+          concat(
+            updateEdgeFetcher,
+            graphql
+          )
+        }
+      }
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/dcaa1f34/s2http/src/main/scala/org/apache/s2graph/http/SangriaGraphQLSupport.scala
----------------------------------------------------------------------
diff --git 
a/s2http/src/main/scala/org/apache/s2graph/http/SangriaGraphQLSupport.scala 
b/s2http/src/main/scala/org/apache/s2graph/http/SangriaGraphQLSupport.scala
new file mode 100644
index 0000000..965e17a
--- /dev/null
+++ b/s2http/src/main/scala/org/apache/s2graph/http/SangriaGraphQLSupport.scala
@@ -0,0 +1,38 @@
+package org.apache.s2graph.http
+
+import java.nio.charset.Charset
+
+import akka.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller}
+import akka.http.scaladsl.model._
+import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, Unmarshaller}
+import akka.util.ByteString
+import sangria.ast.Document
+import sangria.parser.QueryParser
+import sangria.renderer.{QueryRenderer, QueryRendererConfig}
+
+trait SangriaGraphQLSupport {
+  private val mediaTypes: Seq[MediaType.WithFixedCharset] =
+    Seq(MediaType.applicationWithFixedCharset("graphql", HttpCharsets.`UTF-8`, 
"graphql"))
+
+  private val unmarshallerContentTypes: Seq[ContentTypeRange] = 
mediaTypes.map(ContentTypeRange.apply)
+
+  implicit def documentMarshaller(implicit config: QueryRendererConfig = 
QueryRenderer.Compact): ToEntityMarshaller[Document] = {
+    Marshaller.oneOf(mediaTypes: _*) {
+      mediaType ⇒
+        Marshaller.withFixedContentType(ContentType(mediaType)) {
+          json ⇒ HttpEntity(mediaType, QueryRenderer.render(json, config))
+        }
+    }
+  }
+
+  implicit val documentUnmarshaller: FromEntityUnmarshaller[Document] = {
+    Unmarshaller.byteStringUnmarshaller
+      .forContentTypes(unmarshallerContentTypes: _*)
+      .map {
+        case ByteString.empty ⇒ throw Unmarshaller.NoContentException
+        case data ⇒
+          import sangria.parser.DeliveryScheme.Throw
+          QueryParser.parse(data.decodeString(Charset.forName("UTF-8")))
+      }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/dcaa1f34/s2http/src/main/scala/org/apache/s2graph/http/Server.scala
----------------------------------------------------------------------
diff --git a/s2http/src/main/scala/org/apache/s2graph/http/Server.scala 
b/s2http/src/main/scala/org/apache/s2graph/http/Server.scala
index c26e314..00146f6 100644
--- a/s2http/src/main/scala/org/apache/s2graph/http/Server.scala
+++ b/s2http/src/main/scala/org/apache/s2graph/http/Server.scala
@@ -36,7 +36,8 @@ import org.slf4j.LoggerFactory
 object Server extends App
   with S2GraphTraversalRoute
   with S2GraphAdminRoute
-  with S2GraphMutateRoute {
+  with S2GraphMutateRoute
+  with S2GraphQLRoute {
 
   implicit val system: ActorSystem = ActorSystem("S2GraphHttpServer")
   implicit val materializer: ActorMaterializer = ActorMaterializer()
@@ -57,15 +58,20 @@ object Server extends App
     pathPrefix("graphs")(traversalRoute),
     pathPrefix("mutate")(mutateRoute),
     pathPrefix("admin")(adminRoute),
+    pathPrefix("graphql")(graphqlRoute),
     get(complete(health))
   )
 
   val binding: Future[Http.ServerBinding] = Http().bindAndHandle(routes, 
"localhost", port)
   binding.onComplete {
     case Success(bound) => logger.info(s"Server online at 
http://${bound.localAddress.getHostString}:${bound.localAddress.getPort}/";)
-    case Failure(e) =>
-      logger.error(s"Server could not start!", e)
-      system.terminate()
+    case Failure(e) => logger.error(s"Server could not start!", e)
+  }
+
+  scala.sys.addShutdownHook { () =>
+    s2graph.shutdown()
+    system.terminate()
+    logger.info("System terminated")
   }
 
   Await.result(system.whenTerminated, Duration.Inf)

Reply via email to