Skip to content

Commit

Permalink
Merge pull request #8 from websudos/release/NEO-3_embedded_neo4j
Browse files Browse the repository at this point in the history
NEO-3 integration tests
  • Loading branch information
alexflav23 committed Apr 21, 2015
2 parents 2683470 + b877c9d commit bc82927
Show file tree
Hide file tree
Showing 21 changed files with 472 additions and 194 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -25,5 +25,5 @@ jdk:
- oraclejdk7
- openjdk7

script: "sbt test"
script: "sbt \"test-only * -- -l RequiresNeo4jServer\""

33 changes: 30 additions & 3 deletions README.md
Expand Up @@ -85,6 +85,17 @@ class PersonRelation extends Relationship[PersonRelation, Person] {
# Querying
<a href="#table-of-contents">Back to top</a>

## Connection

Prerequisite to making Neo4j requests is REST endpoint definition. This is achived using RestConnection class.

```
scala> implicit val service = RestConnection("localhost", 7474)
service: RestConnection
```

## Making requests

In this example all nodes of Person type are returned.
```
scala> val personNodes = Person().returns(case p ~~ _ => p).execute
Expand All @@ -108,20 +119,23 @@ personNodes: Future[Seq[Person]]

Query for a person that has a relationship to another person
```
scala> val personNodes = (Person() :->: Person()).returns(case p1 ~~ _ => p).execute
scala> val personNodes = (Person() :->: Person())
.returns(case p1 ~~ _ => p).execute
personNodes: Future[Seq[Person]]
```

Query for a person that has a relationship to another person with given name
```
scala> val personNodes = (Person() :->: Person(_.name := "James")).returns(case p ~~ _ => p).execute
scala> val personNodes = (Person() :->: Person(_.name := "James"))
.returns(case p ~~ _ => p).execute
personNodes: Future[Seq[Person]]
```


Query for a person that has a relationship to another person
```
scala> val personNodes = (Person() :<-: WorkRelationship() :->: Person()).returns(case p1 ~~ r ~~ p2 ~~ _ => p1).execute
scala> val personNodes = (Person() :<-: WorkRelationship() :->: Person())
.returns(case p1 ~~ r ~~ p2 ~~ _ => p1).execute
personNodes: Future[Seq[Person]]
```

Expand All @@ -132,3 +146,16 @@ scala> val personNodes = (Person() :-: WorkRelationship(_.company := "ABC") :->:
.returns(case p1 ~~ _ => p1).execute
personNodes: Future[Seq[Person]]
```

## An arbitrary Cypher query
Cypher is a rich language and whenever you need to use it directly escaping the abstraction layer it's still possible
with ReactiveNeo. Use the same REST connection object with an arbitrary Cypher query.
```
scala> val query = "MATCH (n:Person) RETURN n"
query: String
implicit val parser: Reads[Person] = ((__ \ "name").read[String] and (__ \ "age").read[Int])(Person)
parser: Reads[Person]
val result = service.makeRequest[Person](query).execute
result: Future[Seq[Person]]
```
65 changes: 26 additions & 39 deletions project/Build.scala
Expand Up @@ -21,11 +21,15 @@ import org.scalastyle.sbt.ScalastylePlugin

object reactiveneo extends Build {

val UtilVersion = "0.4.0"
val ScalatestVersion = "2.2.0-M1"
val FinagleVersion = "6.20.0"
val playVersion = "2.3.4"
val scalaMajorVersion = "2.11"
val scalaMinorVersion = "6"
val scalaFullVersion = scalaMajorVersion + "." + scalaMinorVersion

val FinagleVersion = "6.24.0"
val playVersion = "2.3.7"
val ScalatestVersion = "2.2.1"
val ScalazVersion = "7.1.0"
val Neo4jVersion = "2.1.7"

val publishUrl = "http://maven.websudos.co.uk"

Expand Down Expand Up @@ -57,7 +61,7 @@ object reactiveneo extends Build {
</scm>
<developers>
<developer>
<id>benjumanji</id>
<id>bjankie1</id>
<name>Bartosz Jankiewicz</name>
<url>http://github.com/bjankie1</url>
</developer>
Expand Down Expand Up @@ -86,7 +90,7 @@ object reactiveneo extends Build {
val sharedSettings: Seq[Def.Setting[_]] = Seq(
organization := "com.websudos",
version := "0.1.2",
scalaVersion := "2.10.4",
scalaVersion := scalaFullVersion,
resolvers ++= Seq(
"Typesafe repository snapshots" at "http://repo.typesafe.com/typesafe/snapshots/",
"Typesafe repository releases" at "http://repo.typesafe.com/typesafe/releases/",
Expand All @@ -112,13 +116,11 @@ object reactiveneo extends Build {
"-unchecked"
),
libraryDependencies ++= Seq(
"com.chuusai" % "shapeless_2.10.4" % "2.0.0",
"com.github.nscala-time" %% "nscala-time" % "1.0.0",
"com.typesafe.scala-logging" %% "scala-logging-slf4j" % "2.1.2",
"com.websudos" %% "util-testing" % UtilVersion % "test",
"org.scalaz" %% "scalaz-scalacheck-binding" % ScalazVersion % "test",
"org.scalatest" %% "scalatest" % ScalatestVersion % "test, provided",
"org.scalamock" %% "scalamock-scalatest-support" % "3.0.1" % "test"
"org.scalamock" %% "scalamock-scalatest-support" % "3.2.1" % "test"
),
fork in Test := true,
javaOptions in Test ++= Seq("-Xmx2G")
Expand All @@ -132,60 +134,45 @@ object reactiveneo extends Build {
name := "ReactiveNeo"
).aggregate(
reactiveneoDsl,
reactiveneoTesting,
reactiveneoZookeeper
reactiveneoTesting
)

lazy val reactiveneoDsl = Project(
id = "reactiveneo-dsl",
base = file("reactiveneo-dsl"),
settings = Defaults.coreDefaultSettings ++
sharedSettings ++
publishSettings
settings = Defaults.coreDefaultSettings ++ sharedSettings ++ publishSettings
).settings(
name := "reactiveneo-dsl",
libraryDependencies ++= Seq(
"com.chuusai" % "shapeless_2.10.4" % "2.0.0",
"org.scala-lang" % "scala-reflect" % "2.10.4",
"org.scala-lang" % "scala-reflect" % scalaFullVersion,
"com.twitter" %% "finagle-http" % FinagleVersion,
"com.twitter" %% "util-core" % FinagleVersion,
"com.twitter" %% "util-core" % "6.23.0",
"joda-time" % "joda-time" % "2.3",
"org.joda" % "joda-convert" % "1.6",
"com.typesafe.play" %% "play-json" % playVersion,
"net.liftweb" %% "lift-json" % "2.6-M4" % "test, provided"
"org.neo4j" % "neo4j-cypher" % Neo4jVersion
)
).dependsOn(
reactiveneoTesting % "test, provided"
)

lazy val reactiveneoZookeeper = Project(
id = "reactiveneo-zookeeper",
base = file("reactiveneo-zookeeper"),
settings = Defaults.coreDefaultSettings ++ sharedSettings
).settings(
name := "reactiveneo-zookeeper",
libraryDependencies ++= Seq(
"com.twitter" %% "finagle-serversets" % FinagleVersion,
"com.twitter" %% "finagle-zookeeper" % FinagleVersion
)
)

lazy val reactiveneoTesting = Project(
id = "reactiveneo-testing",
base = file("reactiveneo-testing"),
settings = Defaults.coreDefaultSettings ++ sharedSettings
settings = Defaults.coreDefaultSettings ++ sharedSettings ++ publishSettings
).settings(
name := "reactiveneo-testing",
libraryDependencies ++= Seq(
"com.twitter" %% "util-core" % FinagleVersion,
"com.websudos" %% "util-testing" % UtilVersion,
"com.twitter" %% "util-core" % "6.23.0",
"com.twitter" %% "finagle-http" % FinagleVersion,
"org.scalatest" %% "scalatest" % ScalatestVersion,
"org.scalacheck" %% "scalacheck" % "1.11.3",
"org.fluttercode.datafactory" % "datafactory" % "0.8",
"com.twitter" %% "finagle-http" % FinagleVersion,
"com.twitter" %% "util-core" % FinagleVersion
)
).dependsOn(
reactiveneoZookeeper
"org.neo4j" % "neo4j-cypher" % Neo4jVersion % "compile",
"org.neo4j" % "neo4j-kernel" % Neo4jVersion % "compile",
"org.neo4j" % "neo4j-kernel" % Neo4jVersion % "compile" classifier "tests"
),
fork in Test := true,
javaOptions in Test ++= Seq("-Xmx2G")
)

}
Expand Up @@ -31,7 +31,7 @@ abstract class AbstractAttribute[@specialized(Int, Double, Float, Long, Boolean,
* @param query Query result data.
* @return Decoded attribute value.
*/
def apply(query: QueryRecord): T
def apply(query: QueryRecord): Option[T]

}

Expand All @@ -47,19 +47,30 @@ abstract class Attribute[Owner <: GraphObject[Owner, R], R, T](val owner: GraphO
class StringAttribute[Owner <: GraphObject[Owner, R], R](graphObject: GraphObject[Owner, R])
extends Attribute[Owner, R, String](graphObject) {

override def apply(query: QueryRecord): String = {
query[String](name).get
override def apply(query: QueryRecord): Option[String] = {
query[String](name)
}

}

/**
* Long attribute definition.
*/
class LongAttribute[Owner <: GraphObject[Owner, R], R](graphObject: GraphObject[Owner, R])
extends Attribute[Owner, R, Long](graphObject) {

override def apply(query: QueryRecord): Option[Long] = {
query[Long](name)
}

}


class IntegerAttribute[Owner <: GraphObject[Owner, R], R](graphObject: GraphObject[Owner, R])
extends Attribute[Owner, R, Int](graphObject) {

override def apply(query: QueryRecord): Int = {
query[Int](name).get
override def apply(query: QueryRecord): Option[Int] = {
query[Int](name)
}

}
@@ -0,0 +1,107 @@
/*
* Copyright 2014 websudos ltd.
* Licensed 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 com.websudos.reactiveneo.client

import java.util.concurrent.TimeUnit

import com.typesafe.scalalogging.slf4j.LazyLogging
import com.websudos.reactiveneo.dsl.MatchQuery
import org.jboss.netty.handler.codec.http.HttpMethod
import play.api.libs.json.Reads

import scala.concurrent.Future
import scala.concurrent.duration.FiniteDuration

/**
* REST API endpoints definitions.
* @param path Server query path.
* @param method HTTP method, with POST as default.
*/
case class RestEndpoint(path: String, method: HttpMethod = HttpMethod.POST)
object SingleTransaction extends RestEndpoint("/db/data/transaction/commit")
object BeginTransaction extends RestEndpoint("/db/data/transaction")
class ContinueInTransaction(transactionId: Int) extends RestEndpoint(s"/db/data/transaction/$transactionId")
class CommitTransaction(transactionId: Int) extends RestEndpoint(s"/db/data/transaction/$transactionId/commit")
class RollbackTransaction(transactionId: Int) extends RestEndpoint(s"/db/data/transaction/$transactionId", HttpMethod.DELETE)

/**
* Model of a call to Neo4j server.
* @tparam RT Type of result call response.
*/
class RestCall[RT](endpoint: RestEndpoint, content: Option[String], resultParser: Reads[RT])(implicit client: RestClient)
extends ServerCall[Seq[RT]]
with LazyLogging {

implicit lazy val parser = {
val parser = new CypherResultParser[RT]()(resultParser)
parser
}

def execute: Future[Seq[RT]] = {
val result = client.makeRequest[Seq[RT]](endpoint.path, endpoint.method, content)
result
}

}

object RestCall {

def apply[RT](endpoint: RestEndpoint, resultParser: Reads[RT], query: String)(implicit client: RestClient) = {
new RestCall[RT](endpoint, Some(query), resultParser)
}

def apply[RT](endpoint: RestEndpoint, resultParser: Reads[RT])(implicit client: RestClient) = {
new RestCall[RT](endpoint, None, resultParser)
}
}

/**
* Service that prepares and executes rest call
*/
class RestConnection(config: ClientConfiguration) {

implicit def client: RestClient = new RestClient(config)

def neoStatement( cypher: String ) =
s"""{
| "statements" : [ {
| "statement" : "$cypher"
| } ]
|}""".stripMargin

implicit def makeRequest[RT](matchQuery: MatchQuery[_, _, _, _, _, RT]): RestCall[RT] = {
val (query, retType) = matchQuery.finalQuery
val requestContent = neoStatement(query)
val call = RestCall(SingleTransaction, retType.resultParser, requestContent)
call
}


implicit def makeRequest[RT](cypher: String)(implicit resultParser: Reads[RT]): RestCall[RT] = {
val requestContent = neoStatement(cypher)
val call = RestCall(SingleTransaction, resultParser, requestContent)
call
}

}

object RestConnection {

def apply(host: String, port: Int): RestConnection = {
val config = ClientConfiguration("localhost", 7474, FiniteDuration(10, TimeUnit.SECONDS))
new RestConnection(config)
}

}
Expand Up @@ -56,6 +56,9 @@ class RestClient(config: ClientConfiguration) extends StrictLogging {
content.foreach { body =>
request.setContent(ChannelBuffers.copiedBuffer(body, Charset.forName("UTF-8")))
}
request.headers()
.add("Content-Type", "application/json")
.add("Host",config.server)

val response: util.Future[HttpResponse] = client(request)
response onSuccess { resp: HttpResponse =>
Expand Down

0 comments on commit bc82927

Please sign in to comment.