diff --git a/doc/doc/datastore/datastore.md b/doc/doc/datastore/datastore.md index ff765b1deb..7057452409 100644 --- a/doc/doc/datastore/datastore.md +++ b/doc/doc/datastore/datastore.md @@ -71,3 +71,7 @@ The [morphia](/doc/morphia) module provides object to document mapper via {{morp ## mongodb-rx The [mongodb-rx](/doc/mongodb-rx) module provides composable asynchronous and event-based observable sequences for {{mongodb}}. + +## neo4j + +The [neo4j](/doc/neo4j) module provides access to [Neo4j](https://neo4j.com) databases. diff --git a/doc/doc/neo4j/neo4j-session.md b/doc/doc/neo4j/neo4j-session.md new file mode 100644 index 0000000000..0ac4027aab --- /dev/null +++ b/doc/doc/neo4j/neo4j-session.md @@ -0,0 +1,66 @@ +# neo4j session store + +A [Session.Store]({{defdocs}}/neo4j/Neo4jSessionStore) powered by Neo4j. + +## dependency + +```xml + + org.jooby + jooby-neo4j + {{version}} + +``` + +## usage + +```java +{ + session(Neo4jSessionStore.class); + + get("/", req -> { + + req.session().set("name", "jooby"); + }); + +} +``` + +The ```name``` attribute and value will be stored in a Neo4j database. + +## options + +### timeout + +By default, a neo4j session will expire after ```30 minutes```. Changing the default timeout is as simple as: + +``` +# 8 hours +session.timeout = 8h +# 15 seconds +session.timeout = 15 +# 120 minutes +session.timeout = 120m +``` + +It uses GraphAware's Expire library to automatically remove expired sessions. + +For embedded databases you need to configure the expire module, like: + +``` +com.graphaware.runtime.enabled = true +com.graphaware.module = [{ + class: com.graphaware.neo4j.expire.ExpirationModuleBootstrapper + nodeExpirationProperty: _expire +}] +``` + +The `Neo4jSessionStore` uses the ```_expire``` attribute to evict sessions. + +If you connect to a remote server make sure the expire module was installed. More information at . + +If no timeout is required, use ```-1```. + +### session label + +It's possible to provide the session label using the ```neo4j.session.label``` property. diff --git a/doc/doc/neo4j/neo4j.md b/doc/doc/neo4j/neo4j.md new file mode 100644 index 0000000000..e9f5d47884 --- /dev/null +++ b/doc/doc/neo4j/neo4j.md @@ -0,0 +1,166 @@ +# neo4j + +Neo4j is a highly scalable native graph database that leverages data relationships as first-class entities, helping enterprises build intelligent applications to meet today’s evolving data challenges. + +This module give you access to neo4j and jcypher APIs. + +## dependency + +```xml + + org.jooby + jooby-neo4j + {{version}} + +``` + +## exports +* GraphDatabaseService for embedded neo4j instances. +* Driver and Session objects for remote instances +* IDBAccess object + +## usage + +```java +{ + use(new Neo4j()); + + get("/driver", () -> { + // work with driver + Driver driver = require(Driver.class); + }); + + get("/session", () -> { + // work with session + Session session = require(Session.class); + }); + + get("/dbaccess", () -> { + // work with driver + BoltDBAccess dbaccess = require(BoltDBAccess.class); + }); +} +``` + +application.conf + +``` +db.url = "bolt://localhost:7687" + db.user = myuser + db.password = mypassword +``` + +## embedded +In addition to remote access using ```bolt``` protocol, this module provide access to ```embedded``` neo4j instances: + +In memory mode: + +```java +{ + use(new Neo4j("mem")); +} +``` + +File system mode: + +```java +{ + use(new Neo4j("fs")); +} +``` + +Optionally you can specify the desired path: + +```java +{ + use(new Neo4j(Paths.get("path", "mydb"))); +} +``` + +The embedded mode allow you to access `GraphDatabaseService` instances: + +```java +{ + use(new Neo4j("mem")); + + get("/", () -> { + GraphDatabaseService db = require(GraphDatabaseService.class); + }); + +} +``` + +As well as `EmbeddedDBAccess`: + +```java +{ + use(new Neo4j("mem")); + + get("/", () -> { + EmbeddedDBAccess db = require(EmbeddedDBAccess.class); + }); + +} +``` + +## runtime modules + +This option is available for ```embedded``` Neo4j instances and we allow to configure one or more runtime modules via ```.conf``` file: + +``` +com.graphaware.runtime.enabled = true +com.graphaware.module = [{ + class: com.graphaware.neo4j.expire.ExpirationModuleBootstrapper + nodeExpirationProperty: _expire +}, { + class: com.graphaware.neo4j.expire.AnotherModule + modProp: modValue +}] +``` + +You first need to ```enabled``` the graph runtime framework by setting the ```com.graphaware.runtime.enabled``` property. + +Then you need to add one or more modules under the ```com.graphaware.module``` property path. + +## two or more connections + +Two or more connection is available by setting and installing multiples {@link Neo4j} modules: + +```java +{ + use(new Neo4j("db1")); + + use(new Neo4j("db2")); + + get("/", () -> { + Driver db1 = require("db1", Driver.class); + BoltDBAccess bolt1 = require("db1", BoltDBAccess.class); + + Driver db2 = require("db2", Driver.class); + BoltDBAccess bolt2 = require("db2", BoltDBAccess.class); + }); +} +``` + +application.conf: + +``` +db1.url = "bolt://localhost:7687" +db1.user = db1user +db1.password = db1pass + +db2.url = "bolt://localhost:7687" +db2.user = db2user +db2.password = db2pass +``` + +## options + +Neo4j options are available via ```.conf``` file: + +``` +neo4j.dbms.read_only = true +neo4j.unsupported.dbms.block_size.array_properties = 120 +``` + +{{doc/neo4j/neo4j-session.md}} diff --git a/doc/doc/session/session.md b/doc/doc/session/session.md index 0f5f6a7537..9a153ccfbd 100644 --- a/doc/doc/session/session.md +++ b/doc/doc/session/session.md @@ -16,4 +16,6 @@ These are the available modules for reading and saving session data in your appl {{doc/mongodb/mongodb-session.md}} +{{doc/neo4j/neo4j-session.md}} + {{doc/spymemcached/spymemcached-session.md}} diff --git a/modules/coverage-report/pom.xml b/modules/coverage-report/pom.xml index 32c1ba31e1..e5dd9d4411 100644 --- a/modules/coverage-report/pom.xml +++ b/modules/coverage-report/pom.xml @@ -90,6 +90,7 @@ ${project.parent.basedir}/jooby-lang-js/src/main/java ${project.parent.basedir}/jooby-requery/src/main/java ${project.parent.basedir}/jooby-rocker/src/main/java + ${project.parent.basedir}/jooby-neo4j/src/main/java @@ -155,6 +156,7 @@ ${project.parent.basedir}/jooby-lang-js/src/test/java ${project.parent.basedir}/jooby-requery/src/test/java ${project.parent.basedir}/jooby-rocker/src/test/java + ${project.parent.basedir}/jooby-neo4j/src/test/java @@ -180,8 +182,10 @@ ${project.parent.basedir}/jooby-hbm/src/test/resources - ${project.parent.basedir}/jooby-whoops/src/test/resources - + ${project.parent.basedir}/jooby-whoops/src/test/resources + + + ${project.parent.basedir}/jooby-neo4j/src/test/resources @@ -400,6 +404,12 @@ ${project.version} + + org.jooby + jooby-neo4j + ${project.version} + + org.jooby jooby-jooq @@ -833,6 +843,11 @@ netty-codec ${netty.version} + + + com.graphaware.neo4j + expire + com.impossibl.pgjdbc-ng diff --git a/modules/jooby-neo4j/docs/embedded-neo4j/embedded-neo4j.md b/modules/jooby-neo4j/docs/embedded-neo4j/embedded-neo4j.md deleted file mode 100644 index 070530d47b..0000000000 --- a/modules/jooby-neo4j/docs/embedded-neo4j/embedded-neo4j.md +++ /dev/null @@ -1,149 +0,0 @@ -# embedded neo4j driver - -[Neo4j](https://neo4j.com/) driver for Jooby. - -## exports - -* [GraphDatabaseService](http://neo4j.com/docs/java-reference/current/javadocs/org/neo4j/graphdb/GraphDatabaseService.html) -* [GraphAwareRuntime](https://graphaware.com/site/framework/latest/apidocs/) - -## dependency - -```xml - - org.jooby - jooby-embedded-neo4j - {{version}} - -``` - -## usage - -application.conf: - -```properties -neo4j.databaseDir = "/tmp" -``` - -```java - { - use(new EmbeddedNeo4j()); - get("/", req -> { - GraphDatabaseService dbService = require(GraphDatabaseService.class); - // work with db - }); - } -``` - -Default database dir property is ```dbService``` but of course you can use any other name: - -application.conf: - -```properties -neo4j.myDbDir = "/tmp" -``` - -```java - { - use(new EmbeddedNeo4j("myDbDir")); - get("/", req -> { - GraphDatabaseService db = require(GraphDatabaseService.class); - // work with db - }); - } -``` - -## properties - -Properties can be set via ```.conf``` file: - -```properties -neo4j.dbms.security.allow_csv_import_from_file_urls = true -``` - -or programmatically: - -```java -{ - use(new EmbeddedNeo4j() - .properties((properties, config) -> { - properties.put(GraphDatabaseSettings.allow_file_urls, true) - }) - ); -} -``` - -# embedded neo4j session store - -## dependency - -```xml - - org.jooby - jooby-embedded-neo4j - {{version}} - -``` - -## usage - -```java -{ - session(EmbeddedNeo4jSessionStore.class); - get("/", req -> { - req.session().set("name", "jooby"); - }); -} -``` - -The ```name``` attribute and value will be stored in a [Neo4j](https://neo4j.com/). - -## properties - -### timeout - -By default, a neo4j session will expire after ```30 minutes```. Changing the default timeout is as simple as: - -```properties -# 8 hours - -session.timeout = 8h - -# 15 seconds - -session.timeout = 15 - -# 120 minutes - -session.timeout = 120m - -# no timeout - -session.timeout = -1 -``` - -It uses [GraphAware's expire library](https://github.com/graphaware/neo4j-expire) to automatically remove expired sessions. - -### session label - -It's possible to provide the session label using the `neo4j.session.label` property. - -## neo4j.conf - -```properties -################################################################################################### - -# neo4j - -################################################################################################### - -neo4j.databaseDir = "/tmp" - -################################################################################################### - -# session datastore - -# neo4j.session.label: "sessions" - -# session.timeout: 30m -``` \ No newline at end of file diff --git a/modules/jooby-neo4j/docs/neo4j/neo4j.md b/modules/jooby-neo4j/docs/neo4j/neo4j.md deleted file mode 100644 index a1159a442a..0000000000 --- a/modules/jooby-neo4j/docs/neo4j/neo4j.md +++ /dev/null @@ -1,110 +0,0 @@ -# neo4j driver - -[Neo4j](https://neo4j.com/) driver for Jooby. - -## exports - -* [BoltDBAccess](https://neo4j.com/developer/java/#jcypher) - -## dependency - -```xml - - org.jooby - jooby-neo4j - {{version}} - -``` - -## usage - -application.conf: - -```properties -neo4j.db { - uri = "bolt://localhost:7687" - username = "neo4j" - password = "neo4j1" -} -``` - -```java - { - use(new Neo4j()); - get("/", req -> { - BoltDBAccess db = require(BoltDBAccess.class); - // work with db - }); - } -``` - -Default connection info property is ```db``` but of course you can use any other name: - -application.conf: - -```properties -neo4j.mydb { - uri = "bolt://localhost:7687" - username = "neo4j" - password = "neo4j1" -} -``` - -```java - { - use(new Neo4j("mydb")); - get("/", req -> { - BoltDBAccess db = require(BoltDBAccess.class); - // work with db - }); - } -``` - -## properties - -Properties can be set via ```.conf``` file: - -```properties -neo4j.db.arrayBlockSize = "120" -``` - -or programmatically: - -```java -{ - use(new Neo4j() - .properties((properties, config) -> { - properties.put(DBProperties.ARRAY_BLOCK_SIZE, "120") - }) - ); -} -``` - -### connection URI - -Default connection URI is defined by the ```neo4j.db.uri``` property. Neo4j URI looks like: - -```properties -neo4j.db.uri = bolt://host1[:port1] -``` - -For more detailed information please check: [Neo4jBoltDrivers](https://neo4j.com/docs/developer-manual/current/drivers/). - -## neo4j.conf - -```properties -################################################################################################### - -# neo4j - -################################################################################################### - -neo4j.db { - server_root_uri = "bolt://localhost:7687" - array_block_size = "120" - pagecache_memory = "1M" - string_block_size = "120" - username = "****" - password = "****" -} -``` \ No newline at end of file diff --git a/modules/jooby-neo4j/pom.xml b/modules/jooby-neo4j/pom.xml index 4d66e0445d..0efb188990 100644 --- a/modules/jooby-neo4j/pom.xml +++ b/modules/jooby-neo4j/pom.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> org.jooby @@ -40,29 +40,49 @@ - com.graphaware.neo4j - expire - 3.1.4.49.4 + org.slf4j + jcl-over-slf4j + com.graphaware.neo4j runtime - 3.1.4.49 + + + commons-logging + commons-logging + + + org.neo4j neo4j-graphdb-api - 3.1.4 + org.neo4j neo4j - 3.1.4 + + + org.neo4j + neo4j-slf4j + + + + org.slf4j + jul-to-slf4j + + + + org.javassist + javassist + + net.iot-solutions.graphdb jcypher - 3.7.0 @@ -109,6 +129,13 @@ commons-lang commons-lang 2.6 + test + + + + com.graphaware.neo4j + expire + test \ No newline at end of file diff --git a/modules/jooby-neo4j/src/main/java/org/jooby/embedded/neo4j/EmbeddedNeo4j.java b/modules/jooby-neo4j/src/main/java/org/jooby/embedded/neo4j/EmbeddedNeo4j.java deleted file mode 100644 index 4a1db855e9..0000000000 --- a/modules/jooby-neo4j/src/main/java/org/jooby/embedded/neo4j/EmbeddedNeo4j.java +++ /dev/null @@ -1,223 +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. - */ -/** - * This copy of Woodstox XML processor is licensed under the - * Apache (Software) License, version 2.0 ("the License"). - * See the License for details about distribution rights, and the - * specific rights regarding derivate works. - * - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/ - * - * A copy is also included in the downloadable source code package - * containing Woodstox, in file "ASL2.0", under the same directory - * as this file. - */ -package org.jooby.embedded.neo4j; - -import com.google.inject.Binder; -import com.graphaware.runtime.GraphAwareRuntime; -import com.graphaware.runtime.GraphAwareRuntimeFactory; -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; -import org.jooby.Env; -import org.jooby.Jooby; -import org.neo4j.graphdb.GraphDatabaseService; -import org.neo4j.graphdb.factory.GraphDatabaseBuilder; -import org.neo4j.graphdb.factory.GraphDatabaseFactory; -import org.neo4j.kernel.configuration.Settings; - -import java.io.File; -import java.util.AbstractMap; -import java.util.Map; -import java.util.Properties; -import java.util.function.BiConsumer; -import java.util.stream.Collectors; - -import static com.google.common.base.Preconditions.checkArgument; -import static java.util.Objects.nonNull; -import static java.util.Objects.requireNonNull; - -/** - * Exposes {@link GraphDatabaseService} and {@link GraphAwareRuntime}. - * - *

usage

- * - *

- * application.conf: - *

- * - *
- * databaseDir = "/tmp"
- * 
- * - *
- * {
- *   use(new EmbeddedNeo4j());
- *
- *   get("/", req {@literal ->} {
- *     // work with db
- *     DB = req.require(GraphDatabaseService.class);
- *   });
- * }
- * 
- * - * Default DatabaseDir property is databaseDir but of course you can use any other name: - * - *

- * application.conf: - *

- * - *
- * mydbDir = "bolt://localhost:7687"
- * 
- * - *
- * {
- *   use(new Neo4j("mydbDir"));
- *
- *   get("/", req {@literal ->} {
- *     DB mydb = req.require(GraphDatabaseService.class);
- *     // work with mydb
- *   });
- * }
- * 
- * - *

options

- *

- * Options can be set via .conf file: - *

- * - *
- * neo4j.dbms.security.allow_csv_import_from_file_urls  = true
- * 
- * - *

- * or programmatically: - *

- * - *
- * {
- *   use(new Neo4j()
- *     .properties((properties, config) {@literal ->} {
- *       properties.put(GraphDatabaseSettings.allow_file_urls, config.get("dbms.security.allow_csv_import_from_file_urls"))
- *     })
- *   );
- * }
- * 
- * - * - * @author sbcd90 - * @since 1.2.0 - */ -public class EmbeddedNeo4j implements Jooby.Module { - private final String databaseDir; - - private BiConsumer properties; - - private final long timeout = 60000L; - - /** - * Creates a new {@link EmbeddedNeo4j} module. - * - * @param databaseDir Database dir location. - */ - public EmbeddedNeo4j(final String databaseDir) { - this.databaseDir = requireNonNull(databaseDir, "A database directory is required"); - } - - /** - * Creates a new {@link EmbeddedNeo4j} using the default property: databaseDir. - */ - public EmbeddedNeo4j() { - this("databaseDir"); - } - - @Override - public void configure(final Env env, final Config config, final Binder binder) { - Properties properties = options(embeddedneo4j(config)); - - if (nonNull(this.properties)) { - this.properties.accept(properties, config); - } - - GraphDatabaseFactory graphDatabaseFactory = new GraphDatabaseFactory(); - GraphDatabaseBuilder builder = graphDatabaseFactory - .newEmbeddedDatabaseBuilder(new File(properties.getProperty("databaseDir"))); - String database = properties.remove("databaseDir").toString(); - - properties.forEach((key, value) -> - builder.setConfig(Settings.setting(key.toString(), Settings.STRING, "default"), value.toString())); - - GraphDatabaseService dbService = builder.newGraphDatabase(); - GraphAwareRuntime graphRuntime = GraphAwareRuntimeFactory.createRuntime(dbService); - checkArgument(dbService.isAvailable(timeout), "Cannot connect to Database"); - - Env.ServiceKey serviceKey = env.serviceKey(); - serviceKey.generate(GraphDatabaseService.class, database, k -> binder.bind(k).toInstance(dbService)); - serviceKey.generate(GraphAwareRuntime.class, database, k -> binder.bind(k).toInstance(graphRuntime)); - - env.onStop(dbService::shutdown); - } - - /** - * Set a properties callback. - * - *
-   * {
-   *   use(new EmbeddedNeo4j()
-   *     .properties((properties, config) {@literal ->} {
-   *       properties.put(GraphDatabaseSettings.allow_file_urls, true);
-   *     })
-   *   );
-   * }
-   * 
- * - * @param properties Configure callback. - * @return This module - */ - public EmbeddedNeo4j properties(final BiConsumer properties) { - this.properties = requireNonNull(properties, "properties callback is required"); - return this; - } - - @Override - public Config config() { - return ConfigFactory.parseResources(EmbeddedNeo4j.class, "embedded_neo4j.conf"); - } - - private Properties options(final Config config) { - Properties properties = new Properties(); - properties.putAll(config.entrySet() - .stream() - .map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), entry.getValue().unwrapped())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); - return properties; - } - - private Config embeddedneo4j(final Config config) { - Config $embeddedNeo4j = config.getConfig("neo4j"); - - if (config.hasPath("neo4j")) { - $embeddedNeo4j = config.getConfig("neo4j").withFallback($embeddedNeo4j); - } - return $embeddedNeo4j; - } -} \ No newline at end of file diff --git a/modules/jooby-neo4j/src/main/java/org/jooby/embedded/neo4j/EmbeddedNeo4jSessionStore.java b/modules/jooby-neo4j/src/main/java/org/jooby/embedded/neo4j/EmbeddedNeo4jSessionStore.java deleted file mode 100644 index 8392ed5360..0000000000 --- a/modules/jooby-neo4j/src/main/java/org/jooby/embedded/neo4j/EmbeddedNeo4jSessionStore.java +++ /dev/null @@ -1,255 +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. - */ -/** - * This copy of Woodstox XML processor is licensed under the - * Apache (Software) License, version 2.0 ("the License"). - * See the License for details about distribution rights, and the - * specific rights regarding derivate works. - * - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/ - * - * A copy is also included in the downloadable source code package - * containing Woodstox, in file "ASL2.0", under the same directory - * as this file. - */ - -package org.jooby.embedded.neo4j; - -import com.google.inject.Inject; -import com.google.inject.name.Named; -import com.graphaware.neo4j.expire.ExpirationModule; -import com.graphaware.neo4j.expire.config.ExpirationConfiguration; -import com.graphaware.runtime.GraphAwareRuntime; -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; -import com.typesafe.config.ConfigValueFactory; -import org.jooby.Session; -import org.neo4j.graphdb.GraphDatabaseService; -import org.neo4j.graphdb.NotFoundException; -import org.neo4j.graphdb.Transaction; -import org.neo4j.graphdb.Label; -import org.neo4j.graphdb.Node; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -import static java.util.Objects.requireNonNull; - -/** - * A {@link Session.Store} powered by - * Neo4j. - * - *

usage

- * - *
- * {
- *
- *   session(EmbeddedNeo4jSessionStore.class);
- *
- *   get("/", req {@literal ->} {
- *    req.session().set("name", "jooby");
- *   });
- * }
- * 
- * - * The name attribute and value will be stored in a - * Neo4j. - * - *

options

- * - *

timeout

- *

- * By default, a neo4j session will expire after 30 minutes. Changing the default - * timeout is as simple as: - *

- * - *
- * # 8 hours
- * session.timeout = 8h
- *
- * # 15 seconds
- * session.timeout = 15
- *
- * # 120 minutes
- * session.timeout = 120m
- * 
- * - *

- * It uses GraphAware's Expire library to automatically remove - * expired sessions. - *

- * - * If no timeout is required, use -1. - * - *

session label

- *

- * It's possible to provide the session label using the neo4j.session.label - * properties. - *

- * - * @author sbcd90 - * @since 1.1.2 - */ -public class EmbeddedNeo4jSessionStore implements Session.Store { - - private static final char DOT = '.'; - - private static final char UDOT = '\uFF0E'; - - private static final char DOLLAR = '$'; - - private static final char UDOLLAR = '\uFF04'; - - protected final String sessionLabel; - - protected final long timeout; - - protected final GraphDatabaseService dbService; - protected final GraphAwareRuntime graphRuntime; - - public EmbeddedNeo4jSessionStore(final GraphDatabaseService dbService, final GraphAwareRuntime graphRuntime, - final String sessionLabel, final long timeoutInSeconds) { - this.dbService = requireNonNull(dbService, "Neo4j Graph Database Service cannot be null"); - this.graphRuntime = requireNonNull(graphRuntime, "Neo4j Graph Aware Runtime cannot be null"); - this.sessionLabel = requireNonNull(sessionLabel, "Neo4j Label to store sessions is required"); - this.timeout = timeoutInSeconds; - - try { - graphRuntime.getModule(ExpirationModule.class); - } catch (NotFoundException ex) { - ExpirationConfiguration configuration = ExpirationConfiguration.defaultConfiguration().withNodeExpirationProperty("_expire"); - graphRuntime.registerModule(new ExpirationModule("EXP", dbService, configuration)); - } - - this.graphRuntime.start(); - this.graphRuntime.waitUntilStarted(); - } - - @Inject - public EmbeddedNeo4jSessionStore(final GraphDatabaseService dbService, - final GraphAwareRuntime graphRuntime, - final @Named("neo4j.session.label") String sessionLabel, - final @Named("session.timeout") String timeout) { - this(dbService, graphRuntime, sessionLabel, seconds(timeout)); - } - - @Override - public Session get(Session.Builder builder) { - try(Transaction tx = dbService.beginTx()) { - return Optional.ofNullable(dbService.findNode(Label.label(sessionLabel), "_id", builder.sessionId())) - .map(node -> { - Map session = new LinkedHashMap<>(node.getAllProperties()); - - Long accessedAt = (Long) session.remove("_accessedAt"); - Long createdAt = (Long) session.remove("_createdAt"); - Long savedAt = (Long) session.remove("_savedAt"); - session.remove("_id"); - session.remove("_expire"); - - builder - .accessedAt(accessedAt) - .createdAt(createdAt) - .savedAt(savedAt); - session.forEach((k ,v) -> builder.set(decode(k), v.toString())); - tx.success(); - return builder.build(); - }).orElse(null); - } - } - - @Override - public void save(Session session) { - String id = session.id(); - Map attributes = session.attributes(); - - try(Transaction tx = dbService.beginTx()) { - Optional.ofNullable(dbService.findNode(Label.label(sessionLabel), "_id", id)) - .map(node -> { - node.setProperty("_accessedAt", session.accessedAt()); - node.setProperty("_createdAt", session.createdAt()); - node.setProperty("_savedAt", session.savedAt()); - - attributes.forEach((k, v) -> node.setProperty(encode(k), v)); - - if (!node.hasProperty("_expire")) { - node.setProperty("_expire", System.currentTimeMillis() + timeout * 1000); - } - - return node; - }).orElseGet(() -> { - Node node = dbService.createNode(Label.label(sessionLabel)); - - node.setProperty("_accessedAt", session.accessedAt()); - node.setProperty("_createdAt", session.createdAt()); - node.setProperty("_savedAt", session.savedAt()); - - attributes.forEach((k, v) -> node.setProperty(encode(k), v)); - node.setProperty("_expire", System.currentTimeMillis() + timeout * 1000); - - return node; - }); - tx.success(); - } - } - - @Override - public void create(Session session) { - save(session); - } - - @Override - public void delete(String id) { - try(Transaction tx = dbService.beginTx()) { - dbService.findNode(Label.label(sessionLabel), "_id", id).delete(); - tx.success(); - } - } - - private static long seconds(final String value) { - try { - return Long.parseLong(value); - } catch (NumberFormatException ex) { - Config config = ConfigFactory.empty() - .withValue("timeout", ConfigValueFactory.fromAnyRef(value)); - return config.getDuration("timeout", TimeUnit.SECONDS); - } catch (NullPointerException ex) { - return 1800L; - } - } - - private String encode(final String key) { - String value = key; - if (value.charAt(0) == DOLLAR) { - value = UDOLLAR + value.substring(1); - } - return value.replace(DOT, UDOT); - } - - private String decode(final String key) { - String value = key; - if (value.charAt(0) == UDOLLAR) { - value = DOLLAR + value.substring(1); - } - return value.replace(UDOT, DOT); - } -} \ No newline at end of file diff --git a/modules/jooby-neo4j/src/main/java/org/jooby/neo4j/Neo4j.java b/modules/jooby-neo4j/src/main/java/org/jooby/neo4j/Neo4j.java index 2ef72ee51e..e0266164a4 100644 --- a/modules/jooby-neo4j/src/main/java/org/jooby/neo4j/Neo4j.java +++ b/modules/jooby-neo4j/src/main/java/org/jooby/neo4j/Neo4j.java @@ -1,239 +1,656 @@ /** - * 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. + * Apache License + * Version 2.0, January 2004 + * http://www.apache.org/licenses/ + * + * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + * + * 1. Definitions. + * + * "License" shall mean the terms and conditions for use, reproduction, + * and distribution as defined by Sections 1 through 9 of this document. + * + * "Licensor" shall mean the copyright owner or entity authorized by + * the copyright owner that is granting the License. + * + * "Legal Entity" shall mean the union of the acting entity and all + * other entities that control, are controlled by, or are under common + * control with that entity. For the purposes of this definition, + * "control" means (i) the power, direct or indirect, to cause the + * direction or management of such entity, whether by contract or + * otherwise, or (ii) ownership of fifty percent (50%) or more of the + * outstanding shares, or (iii) beneficial ownership of such entity. + * + * "You" (or "Your") shall mean an individual or Legal Entity + * exercising permissions granted by this License. + * + * "Source" form shall mean the preferred form for making modifications, + * including but not limited to software source code, documentation + * source, and configuration files. + * + * "Object" form shall mean any form resulting from mechanical + * transformation or translation of a Source form, including but + * not limited to compiled object code, generated documentation, + * and conversions to other media types. + * + * "Work" shall mean the work of authorship, whether in Source or + * Object form, made available under the License, as indicated by a + * copyright notice that is included in or attached to the work + * (an example is provided in the Appendix below). + * + * "Derivative Works" shall mean any work, whether in Source or Object + * form, that is based on (or derived from) the Work and for which the + * editorial revisions, annotations, elaborations, or other modifications + * represent, as a whole, an original work of authorship. For the purposes + * of this License, Derivative Works shall not include works that remain + * separable from, or merely link (or bind by name) to the interfaces of, + * the Work and Derivative Works thereof. + * + * "Contribution" shall mean any work of authorship, including + * the original version of the Work and any modifications or additions + * to that Work or Derivative Works thereof, that is intentionally + * submitted to Licensor for inclusion in the Work by the copyright owner + * or by an individual or Legal Entity authorized to submit on behalf of + * the copyright owner. For the purposes of this definition, "submitted" + * means any form of electronic, verbal, or written communication sent + * to the Licensor or its representatives, including but not limited to + * communication on electronic mailing lists, source code control systems, + * and issue tracking systems that are managed by, or on behalf of, the + * Licensor for the purpose of discussing and improving the Work, but + * excluding communication that is conspicuously marked or otherwise + * designated in writing by the copyright owner as "Not a Contribution." + * + * "Contributor" shall mean Licensor and any individual or Legal Entity + * on behalf of whom a Contribution has been received by Licensor and + * subsequently incorporated within the Work. + * + * 2. Grant of Copyright License. Subject to the terms and conditions of + * this License, each Contributor hereby grants to You a perpetual, + * worldwide, non-exclusive, no-charge, royalty-free, irrevocable + * copyright license to reproduce, prepare Derivative Works of, + * publicly display, publicly perform, sublicense, and distribute the + * Work and such Derivative Works in Source or Object form. + * + * 3. Grant of Patent License. Subject to the terms and conditions of + * this License, each Contributor hereby grants to You a perpetual, + * worldwide, non-exclusive, no-charge, royalty-free, irrevocable + * (except as stated in this section) patent license to make, have made, + * use, offer to sell, sell, import, and otherwise transfer the Work, + * where such license applies only to those patent claims licensable + * by such Contributor that are necessarily infringed by their + * Contribution(s) alone or by combination of their Contribution(s) + * with the Work to which such Contribution(s) was submitted. If You + * institute patent litigation against any entity (including a + * cross-claim or counterclaim in a lawsuit) alleging that the Work + * or a Contribution incorporated within the Work constitutes direct + * or contributory patent infringement, then any patent licenses + * granted to You under this License for that Work shall terminate + * as of the date such litigation is filed. + * + * 4. Redistribution. You may reproduce and distribute copies of the + * Work or Derivative Works thereof in any medium, with or without + * modifications, and in Source or Object form, provided that You + * meet the following conditions: + * + * (a) You must give any other recipients of the Work or + * Derivative Works a copy of this License; and + * + * (b) You must cause any modified files to carry prominent notices + * stating that You changed the files; and + * + * (c) You must retain, in the Source form of any Derivative Works + * that You distribute, all copyright, patent, trademark, and + * attribution notices from the Source form of the Work, + * excluding those notices that do not pertain to any part of + * the Derivative Works; and + * + * (d) If the Work includes a "NOTICE" text file as part of its + * distribution, then any Derivative Works that You distribute must + * include a readable copy of the attribution notices contained + * within such NOTICE file, excluding those notices that do not + * pertain to any part of the Derivative Works, in at least one + * of the following places: within a NOTICE text file distributed + * as part of the Derivative Works; within the Source form or + * documentation, if provided along with the Derivative Works; or, + * within a display generated by the Derivative Works, if and + * wherever such third-party notices normally appear. The contents + * of the NOTICE file are for informational purposes only and + * do not modify the License. You may add Your own attribution + * notices within Derivative Works that You distribute, alongside + * or as an addendum to the NOTICE text from the Work, provided + * that such additional attribution notices cannot be construed + * as modifying the License. + * + * You may add Your own copyright statement to Your modifications and + * may provide additional or different license terms and conditions + * for use, reproduction, or distribution of Your modifications, or + * for any such Derivative Works as a whole, provided Your use, + * reproduction, and distribution of the Work otherwise complies with + * the conditions stated in this License. + * + * 5. Submission of Contributions. Unless You explicitly state otherwise, + * any Contribution intentionally submitted for inclusion in the Work + * by You to the Licensor shall be under the terms and conditions of + * this License, without any additional terms or conditions. + * Notwithstanding the above, nothing herein shall supersede or modify + * the terms of any separate license agreement you may have executed + * with Licensor regarding such Contributions. + * + * 6. Trademarks. This License does not grant permission to use the trade + * names, trademarks, service marks, or product names of the Licensor, + * except as required for reasonable and customary use in describing the + * origin of the Work and reproducing the content of the NOTICE file. + * + * 7. Disclaimer of Warranty. Unless required by applicable law or + * agreed to in writing, Licensor provides the Work (and each + * Contributor provides its Contributions) on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied, including, without limitation, any warranties or conditions + * of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + * PARTICULAR PURPOSE. You are solely responsible for determining the + * appropriateness of using or redistributing the Work and assume any + * risks associated with Your exercise of permissions under this License. + * + * 8. Limitation of Liability. In no event and under no legal theory, + * whether in tort (including negligence), contract, or otherwise, + * unless required by applicable law (such as deliberate and grossly + * negligent acts) or agreed to in writing, shall any Contributor be + * liable to You for damages, including any direct, indirect, special, + * incidental, or consequential damages of any character arising as a + * result of this License or out of the use or inability to use the + * Work (including but not limited to damages for loss of goodwill, + * work stoppage, computer failure or malfunction, or any and all + * other commercial damages or losses), even if such Contributor + * has been advised of the possibility of such damages. + * + * 9. Accepting Warranty or Additional Liability. While redistributing + * the Work or Derivative Works thereof, You may choose to offer, + * and charge a fee for, acceptance of support, warranty, indemnity, + * or other liability obligations and/or rights consistent with this + * License. However, in accepting such obligations, You may act only + * on Your own behalf and on Your sole responsibility, not on behalf + * of any other Contributor, and only if You agree to indemnify, + * defend, and hold each Contributor harmless for any liability + * incurred by, or claims asserted against, such Contributor by reason + * of your accepting any such warranty or additional liability. + * + * END OF TERMS AND CONDITIONS + * + * APPENDIX: How to apply the Apache License to your work. + * + * To apply the Apache License to your work, attach the following + * boilerplate notice, with the fields enclosed by brackets "{}" + * replaced with your own identifying information. (Don't include + * the brackets!) The text should be enclosed in the appropriate + * comment syntax for the file format. We also recommend that a + * file or class name and description of purpose be included on the + * same "printed page" as the copyright notice for easier + * identification within third-party archives. + * + * Copyright 2014 Edgar Espina + * + * 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 org.jooby.neo4j; +import static iot.jcypher.database.DBProperties.DATABASE_DIR; +import static iot.jcypher.database.DBProperties.SERVER_ROOT_URI; +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicInteger; + +import org.jooby.Env; +import org.jooby.Env.ServiceKey; +import org.jooby.Jooby.Module; +import org.neo4j.driver.v1.AuthTokens; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.GraphDatabase; +import org.neo4j.driver.v1.Session; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.factory.GraphDatabaseBuilder; +import org.neo4j.graphdb.factory.GraphDatabaseFactory; +import org.neo4j.logging.slf4j.Slf4jLogProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.bridge.SLF4JBridgeHandler; + import com.google.inject.Binder; import com.typesafe.config.Config; +import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigFactory; -import iot.jcypher.database.DBAccessFactory; +import com.typesafe.config.ConfigValue; +import com.typesafe.config.ConfigValueFactory; +import com.typesafe.config.ConfigValueType; + import iot.jcypher.database.DBProperties; -import iot.jcypher.database.DBType; +import iot.jcypher.database.IDBAccess; +import iot.jcypher.database.embedded.AbstractEmbeddedDBAccess; +import iot.jcypher.database.embedded.EmbeddedDBAccess; import iot.jcypher.database.remote.BoltDBAccess; -import org.jooby.Env; -import org.jooby.Jooby; - -import java.util.AbstractMap; -import java.util.Map; -import java.util.Properties; -import java.util.function.BiConsumer; -import java.util.stream.Collectors; - -import static com.google.common.base.Preconditions.checkArgument; -import static java.util.Objects.nonNull; -import static java.util.Objects.requireNonNull; /** - * Exposes {@link BoltDBAccess}. - * - *

usage

+ *

neo4j

+ *

+ * Neo4j is a highly scalable native graph database that leverages + * data relationships as first-class entities, helping enterprises build intelligent applications to + * meet today’s evolving data challenges. + *

* *

- * application.conf: + * This module give you access to neo4j and + * jcypher APIs. *

* - *
- * db = "bolt://localhost:7687"
- * 
+ *

exports

+ *
    + *
  • GraphDatabaseService for embedded neo4j instances.
  • + *
  • Driver and Session objects for remote instances
  • + *
  • IDBAccess object
  • + *
* - *
+ * 

usage

+ * + *
{@code
  * {
  *   use(new Neo4j());
  *
- *   get("/", req {@literal ->} {
- *     // work with db
- *     DB = req.require(BoltDBAccess.class);
+ *   get("/driver", () -> {
+ *     // work with driver
+ *     Driver driver = require(Driver.class);
  *   });
- * }
- * 
* - * Default DB connection info property is db but of course you can use any other name: + * get("/session", () -> { + * // work with session + * Session session = require(Session.class); + * }); + * + * get("/dbaccess", () -> { + * // work with driver + * BoltDBAccess dbaccess = require(BoltDBAccess.class); + * }); + * } + * }
* *

- * application.conf: + * application.conf *

- * *
- * mydb = "bolt://localhost:7687"
+ *  db.url = "bolt://localhost:7687"
+ *  db.user = myuser
+ *  db.password = mypassword
  * 
* - *
- * {
- *   use(new Neo4j("mydb"));
+ * 

embedded

+ *

+ * In addition to remote access using bolt protocol, this module provide + * access to embedded neo4j instances: + *

* - * get("/", req {@literal ->} { - * DB mydb = req.require(DB.class); - * // work with mydb - * }); + *

+ * In memory mode: + *

+ *
{@code
+ * {
+ *   use(new Neo4j("mem"));
  * }
- * 
+ * }
* - *

options

*

- * Options can be set via .conf file: + * File system mode: *

+ *
{@code
+ * {
+ *   use(new Neo4j("fs"));
+ * }
+ * }
* - *
- * neo4j.server_root_uri  = "bolt://localhost:7687"
- * 
+ *

+ * Optionally you can specify the desired path: + *

+ *
{@code
+ * {
+ *   use(new Neo4j(Paths.get("path", "mydb")));
+ * }
+ * }
* *

- * or programmatically: + * The embedded mode allow you to access {@link GraphDatabaseService} instances: *

* - *
+ * 
{@code
  * {
- *   use(new Neo4j()
- *     .properties((properties, config) {@literal ->} {
- *       properties.put(DBProperties.SERVER_ROOT_URI, config.get("server_root_uri"))
- *     })
- *   );
+ *   use(new Neo4j("mem"));
+ *
+ *   get("/", () -> {
+ *     GraphDatabaseService db = require(GraphDatabaseService.class);
+ *   });
  * }
- * 
+ * }
* *

- * Default connection URI is defined by the server_root_uri property. Neo4j URI looks like: + * As well as {@link EmbeddedDBAccess}: + *

+ *
{@code
+ * {
+ *   use(new Neo4j("mem"));
+ *
+ *   get("/", () -> {
+ *     EmbeddedDBAccess db = require(EmbeddedDBAccess.class);
+ *   });
+ * }
+ * }
+ * + *

runtime modules

+ *

+ * This option is available for embedded Neo4j instances and we allow to configure one + * or more runtime modules via .conf file: *

* *
- *   bolt://host1[:port1]
+ * com.graphaware.runtime.enabled = true
+ *
+ * com.graphaware.module = [{
+ *   class: com.graphaware.neo4j.expire.ExpirationModuleBootstrapper
+ *   nodeExpirationProperty: _expire
+ * }, {
+ *   class: com.graphaware.neo4j.expire.AnotherModule
+ *   modProp: modValue
+ * }]
  * 
* *

- * Credentials have to be passed separately in username & password params. + * You first need to enabled the graph runtime framework by setting the + * com.graphaware.runtime.enabled property. *

* - *

two or more connections

+ *

+ * Then you need to add one or more modules under the com.graphaware.module + * property path. + *

* - *
- * db1 = "bolt://localhost:7687"
- * db2 = "bolt://localhost:7688"
- * 
+ *

two or more connections

+ *

+ * Two or more connection is available by setting and installing multiples {@link Neo4j} modules: + *

* - *
+ * 
{@code
  * {
  *   use(new Neo4j("db1"));
+ *
  *   use(new Neo4j("db2"));
  *
- *   get("/", req {@literal ->} {
- *     BoltDBAccess client1 = req.require("mydb1", BoltDBAccess.class);
- *     // work with mydb1
- *     BoltDBAccess client2 = req.require("mydb2", BoltDBAccess.class);
- *     // work with mydb1
+ *   get("/", () -> {
+ *     Driver db1 = require("db1", Driver.class);
+ *     BoltDBAccess bolt1 = require("db1", BoltDBAccess.class);
+ *
+ *     Driver db2 = require("db2", Driver.class);
+ *     BoltDBAccess bolt2 = require("db2", BoltDBAccess.class);
  *   });
  * }
+ * }
+ * + *

+ * application.conf: + *

+ * + *
+ *  db1.url = "bolt://localhost:7687"
+ *  db1.user = db1user
+ *  db1.password = db1pass
+ *
+ *  db2.url = "bolt://localhost:7687"
+ *  db2.user = db2user
+ *  db2.password = db2pass
  * 
* + *

options

+ *

+ * Neo4j options are available via .conf file: + *

+ * + *
+ * neo4j.dbms.read_only = true
+ * neo4j.unsupported.dbms.block_size.array_properties = 120
+ * 
+ * + * @author edgar * @author sbcd90 - * @since 1.2.0 */ -public class Neo4j implements Jooby.Module { +public class Neo4j implements Module { + + static { + if (!SLF4JBridgeHandler.isInstalled()) { + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + } + } - private final String db; + /** The logging system. */ + private final Logger log = LoggerFactory.getLogger(getClass()); - private BiConsumer properties; + private String db; /** * Creates a new {@link Neo4j} module. * - * @param db Name of the property with the connection info. + * @param db Database type: mem, fs, + * path for embedded database or property pointing the + * a database server or one of the previously described values. */ public Neo4j(final String db) { - this.db = requireNonNull(db, "A neo4j bolt configuration is required"); + this.db = requireNonNull(db, "Database required."); + } + + /** + * Creates a new embedded {@link Neo4j} module. + * + * @param db Path to store the database. + */ + public Neo4j(final Path db) { + this(db.toAbsolutePath().toString()); } /** - * Creates a new {@link Neo4j} using the default property: db. + * Creates a new {@link Neo4j} module a db property must be defined in your + * .conf file. Property value must be one of: mem, fs, + * file system path or bolt url. + * */ public Neo4j() { this("db"); } + @SuppressWarnings({"unchecked", "rawtypes" }) @Override - public void configure(final Env env, final Config config, final Binder binder) { - Properties properties = options(neo4j(config)); + public void configure(final Env env, final Config conf, final Binder binder) throws Throwable { + String db = database(conf, this.db); + Properties props = props(neo4j(conf, this.db)); - if (nonNull(this.properties)) { - this.properties.accept(properties, config); - } + ServiceKey keys = env.serviceKey(); + IDBAccess dbaccess = dbaccess(conf, this.db, db, props, keys, binder); + + Arrays.asList(props.getProperty(SERVER_ROOT_URI), props.getProperty(DATABASE_DIR)) + .stream() + .filter(Objects::nonNull) + .findFirst() + .ifPresent(it -> log.info("Starting neo4j: {}", it)); + + Class dbaccessType = dbaccess.getClass(); + keys.generate(IDBAccess.class, this.db, k -> binder.bind(k).toInstance(dbaccess)); + keys.generate(dbaccessType, this.db, k -> binder.bind(k).toInstance(dbaccess)); + + env.onStop(dbaccess::close); + } + + @Override + public Config config() { + return ConfigFactory.empty(getClass().getName().toLowerCase() + ".conf") + .withValue("neo4j.session.label", ConfigValueFactory.fromAnyRef("session")); + } - String dbUser = nonNull(properties.getProperty("username")) ? properties.getProperty("username"): ""; - String dbPassword = nonNull(properties.getProperty("password")) ? properties.getProperty("password"): ""; + private Config neo4j(final Config conf, final String db) { + Config result = conf.hasPath("com.graphaware") + ? ConfigFactory.empty().withValue("com.graphaware", conf.getConfig("com.graphaware").root()) + : ConfigFactory.empty(); + String[] paths = {"neo4j", db, "neo4j." + db }; + for (String path : paths) { + try { + if (conf.hasPath(path)) { + Config it = conf.getConfig(path); + result = it.withFallback(result); + } + } catch (ConfigException x) { + // Skip/ignore bad path + } + } + return result; + } - BoltDBAccess remoteClient; - if (dbUser.isEmpty() && dbPassword.isEmpty()) { - remoteClient = (BoltDBAccess) DBAccessFactory.createDBAccess(DBType.REMOTE, properties); - } else { - remoteClient = (BoltDBAccess) DBAccessFactory.createDBAccess(DBType.REMOTE, properties, dbUser, dbPassword); + @SuppressWarnings("deprecation") + private IDBAccess dbaccess(final Config conf, final String dbkey, final String db, + final Properties props, final Env.ServiceKey keys, final Binder binder) throws Exception { + // remote + if (db.startsWith("bolt")) { + String username = conf.getString(dbkey + ".user"); + String password = conf.getString(dbkey + ".password"); + Driver driver = GraphDatabase.driver(db, AuthTokens.basic(username, password)); + Session session = driver.session(); + props.setProperty(DBProperties.SERVER_ROOT_URI, db); + keys.generate(Driver.class, dbkey, k -> binder.bind(k).toInstance(driver)); + keys.generate(Session.class, dbkey, k -> binder.bind(k).toInstance(session)); + BoltDBAccess dbaccess = new BoltDBAccess(); + dbaccess.initialize(props); + driverhack(dbaccess, driver); + return dbaccess; + } + boolean mem = db.equals("mem"); + Path path = (db.equals("fs") || mem + ? Paths.get(conf.getString("application.tmpdir")).resolve("neo4j" + db) + : Paths.get(db)).toAbsolutePath(); + props.setProperty(DBProperties.DATABASE_DIR, path.toString()); + if (mem) { + // clean up existing database + rm(path); } - checkArgument(remoteClient.getSession() != null, - "Cannot connect to Database at: " + properties.get(DBProperties.SERVER_ROOT_URI)); - String database = properties.getProperty(DBProperties.SERVER_ROOT_URI); + GraphDatabaseBuilder builder = new GraphDatabaseFactory() + .setUserLogProvider(new Slf4jLogProvider()) + .newEmbeddedDatabaseBuilder(path.toFile()); + props.forEach((k, v) -> builder.setConfig(k.toString(), v.toString())); + GraphDatabaseService graphDatabaseService = builder.newGraphDatabase(); + keys.generate(GraphDatabaseService.class, dbkey, + k -> binder.bind(k).toInstance(graphDatabaseService)); - Env.ServiceKey serviceKey = env.serviceKey(); - serviceKey.generate(BoltDBAccess.class, database, k -> binder.bind(k).toInstance(remoteClient)); + EmbeddedDBAccess dbaccess = new EmbeddedDBAccess(); + dbaccess.initialize(props); + dbServicehack(dbaccess, graphDatabaseService); - env.onStop(remoteClient::close); + return dbaccess; } - /** - * Set a properties callback. - * - *
-   * {
-   *   use(new Neo4j()
-   *     .properties((properties, config) {@literal ->} {
-   *       properties.put(DBProperties.ARRAY_BLOCK_SIZE, "120);
-   *     })
-   *   );
-   * }
-   * 
- * - * @param properties Configure callback. - * @return This module - */ - public Neo4j properties(final BiConsumer properties) { - this.properties = requireNonNull(properties, "properties callback is required"); - return this; + private String database(final Config conf, final String db) { + String[] paths = {db + ".url", db }; + for (String path : paths) { + if (conf.hasPath(path)) { + return conf.getString(path); + } + } + if (db.equals("db")) { + throw new ConfigException.Missing(db); + } + // path + return db; } - @Override - public Config config() { - return ConfigFactory.parseResources(Neo4j.class, "neo4j.conf"); + private Properties props(final Config conf) { + Properties props = new Properties(); + conf.entrySet().stream() + .filter(it -> !it.getKey().startsWith("com.graphaware.module")) + .forEach(prop -> { + String key = prop.getKey(); + Object value = prop.getValue().unwrapped(); + props.put(key, value); + }); + // write module: + if (conf.hasPath("com.graphaware.module")) { + ConfigValue raw = conf.getValue("com.graphaware.module"); + List modules = new ArrayList<>(); + if (raw.valueType() == ConfigValueType.LIST) { + modules.addAll(conf.getConfigList("com.graphaware.module")); + } else { + modules.add(conf.getConfig("com.graphaware.module")); + } + AtomicInteger nextOrder = new AtomicInteger(0); + modules.forEach(module -> { + String type = module.getString("class"); + int order = nextOrder.incrementAndGet(); + String key = "com.graphaware.module.m" + order; + props.put(key + "." + order, type); + module.withoutPath("class").root() + .forEach((k, v) -> props.put(key + "." + k, v.unwrapped().toString())); + }); + } + log.debug("neo4j properties: {}", props); + return props; } - private Properties options(final Config config) { - Properties properties = new Properties(); - properties.putAll(config.entrySet() - .stream() - .map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), entry.getValue().unwrapped())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + static void driverhack(final BoltDBAccess dbaccess, final Driver value) throws Exception { + fieldhack(BoltDBAccess.class, dbaccess, "driver", value); + } + + static void dbServicehack(final EmbeddedDBAccess dbaccess, final GraphDatabaseService value) + throws Exception { + fieldhack(AbstractEmbeddedDBAccess.class, dbaccess, "graphDb", value); + } - return properties; + @SuppressWarnings("rawtypes") + static void fieldhack(final Class owner, final Object instance, final String name, + final Object value) throws Exception { + Field field = owner.getDeclaredField(name); + field.setAccessible(true); + field.set(instance, value); } - private Config neo4j(final Config config) { - Config $neo4j = config.getConfig("neo4j"); + static void rm(final Path path) throws IOException { + if (Files.exists(path)) { + Files.walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) + throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) + throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } - if (config.hasPath("neo4j." + db)) { - $neo4j = config.getConfig("neo4j." + db).withFallback($neo4j); + }); } - return $neo4j; } -} \ No newline at end of file +} diff --git a/modules/jooby-neo4j/src/main/java/org/jooby/neo4j/Neo4jSessionStore.java b/modules/jooby-neo4j/src/main/java/org/jooby/neo4j/Neo4jSessionStore.java index 7ffe36198f..fc9db71b52 100644 --- a/modules/jooby-neo4j/src/main/java/org/jooby/neo4j/Neo4jSessionStore.java +++ b/modules/jooby-neo4j/src/main/java/org/jooby/neo4j/Neo4jSessionStore.java @@ -19,31 +19,41 @@ package org.jooby.neo4j; -import com.graphaware.neo4j.expire.ExpirationModule; -import com.graphaware.neo4j.expire.config.ExpirationConfiguration; -import com.graphaware.runtime.GraphAwareRuntime; -import com.graphaware.runtime.GraphAwareRuntimeFactory; -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; -import com.typesafe.config.ConfigValueFactory; -import org.apache.commons.lang3.tuple.Pair; -import org.jooby.Session; -import org.neo4j.graphdb.GraphDatabaseService; -import org.neo4j.graphdb.Label; -import org.neo4j.graphdb.Node; -import org.neo4j.graphdb.Transaction; -import org.neo4j.graphdb.factory.GraphDatabaseBuilder; -import org.neo4j.graphdb.factory.GraphDatabaseFactory; +import static java.util.Objects.requireNonNull; -import javax.inject.Inject; -import javax.inject.Named; -import java.io.File; -import java.util.LinkedHashMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.Optional; +import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.LongSupplier; +import java.util.stream.Collectors; -import static java.util.Objects.requireNonNull; +import javax.inject.Inject; +import javax.inject.Named; + +import org.jooby.Session; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableSet; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import com.typesafe.config.ConfigValueFactory; + +import iot.jcypher.database.IDBAccess; +import iot.jcypher.graph.GrNode; +import iot.jcypher.graph.GrProperty; +import iot.jcypher.query.JcQuery; +import iot.jcypher.query.JcQueryResult; +import iot.jcypher.query.api.IClause; +import iot.jcypher.query.api.pattern.Node; +import iot.jcypher.query.factories.clause.CREATE; +import iot.jcypher.query.factories.clause.DO; +import iot.jcypher.query.factories.clause.MATCH; +import iot.jcypher.query.factories.clause.RETURN; +import iot.jcypher.query.values.JcNode; /** * A {@link Session.Store} powered by @@ -85,16 +95,39 @@ *
* *

- * It uses GraphAware's Expire library to automatically remove - * expired sessions. + * It uses GraphAware's Expire library to + * automatically remove expired sessions. + *

+ * + *

+ * For embedded databases you need to configure the expire module, like: + *

+ * + *
+ * com.graphaware.runtime.enabled = true
+ *
+ * com.graphaware.module = [{
+ *   class: com.graphaware.neo4j.expire.ExpirationModuleBootstrapper
+ *   nodeExpirationProperty: _expire
+ * }]
+ * 
+ * + *

+ * The {@link Neo4jSessionStore} uses the _expire to evict sessions. *

* + *

+ * If you connect to a remote server make sure the expire module was installed. More information at + * . + *

+ * + *

* If no timeout is required, use -1. + *

* *

session label

*

- * It's possible to provide the session label using the neo4j.session.label - * properties. + * It's possible to provide the session label using the neo4j.session.label properties. *

* * @author sbcd90 @@ -102,110 +135,130 @@ */ public class Neo4jSessionStore implements Session.Store { - private static final char DOT = '.'; - - private static final char UDOT = '\uFF0E'; - - private static final char DOLLAR = '$'; - - private static final char UDOLLAR = '\uFF04'; - - protected final String sessionLabel; + private final Set SPECIAL = ImmutableSet.of("_accessedAt", "_createdAt", "_savedAt", + "_expire", "_id"); - protected final long timeout; + /** The logging system. */ + private final Logger log = LoggerFactory.getLogger(getClass()); - private static GraphDatabaseService dbService; - private static GraphAwareRuntime graphRuntime; + private final String label; - protected final GraphDatabaseService db; - protected final GraphAwareRuntime graphAwareRuntime; + private final LongSupplier expire; + private final IDBAccess db; - public Neo4jSessionStore(Pair graph, final String sessionLabel, - final long timeoutInSeconds) { - this.db = requireNonNull(graph.getLeft(), "GraphDatabaseService instance is required"); - this.graphAwareRuntime = requireNonNull(graph.getRight(), "GraphAwareRuntime instance is required"); - this.sessionLabel = requireNonNull(sessionLabel, "Label to store sessions is required"); - this.timeout = timeoutInSeconds; + public Neo4jSessionStore(final IDBAccess dbaccess, final String sessionLabel, + final long timeoutInSeconds) { + this.db = dbaccess; + this.label = requireNonNull(sessionLabel, "Label to store sessions is required"); + long expire = TimeUnit.SECONDS.toMillis(timeoutInSeconds); + this.expire = () -> System.currentTimeMillis() + expire; } @Inject - public Neo4jSessionStore(@Named("databaseDir") String databaseDir, - final @Named("neo4j.session.label") String sessionLabel, - final @Named("session.timeout") String timeout) { - this(getOrCreateGraph(databaseDir), sessionLabel, seconds(timeout)); + public Neo4jSessionStore(final IDBAccess dbaccess, + final @Named("neo4j.session.label") String sessionLabel, + final @Named("session.timeout") String timeout) { + this(dbaccess, sessionLabel, seconds(timeout)); } @Override public Session get(final Session.Builder builder) { - try(Transaction tx = db.beginTx()) { - return Optional.ofNullable(db.findNode(Label.label(sessionLabel), "_id", builder.sessionId())) - .map(node -> { - Map session = new LinkedHashMap<>(node.getAllProperties()); - - Long accessedAt = (Long) session.remove("_accessedAt"); - Long createdAt = (Long) session.remove("_createdAt"); - Long savedAt = (Long) session.remove("_savedAt"); - session.remove("_id"); - session.remove("_expire"); - - builder - .accessedAt(accessedAt) - .createdAt(createdAt) - .savedAt(savedAt); - session.forEach((k, v) -> builder.set(decode(k), v.toString())); - tx.success(); - return builder.build(); - }).orElse(null); + String sid = builder.sessionId(); + JcNode node = new JcNode("n"); + JcQuery query = new JcQuery(); + query.setClauses(new IClause[]{ + MATCH.node(node).label(label).property("_id").value(sid), + // touch session + DO.SET(node.property("_expire")).to(expire.getAsLong()), + RETURN.value(node) + }); + + List result = db.execute(query).resultOf(node); + log.debug("touch {} session {} ", sid, result); + if (result.size() == 1) { + GrNode found = result.get(0); + builder + .accessedAt(((Number) found.getProperty("_accessedAt").getValue()).longValue()) + .createdAt(((Number) found.getProperty("_createdAt").getValue()).longValue()) + .savedAt(((Number) found.getProperty("_savedAt").getValue()).longValue()); + + found.getProperties() + .stream() + .filter(it -> !SPECIAL.contains(it.getName())) + .forEach(p -> builder.set(p.getName(), p.getValue().toString())); + + return builder.build(); } + + return null; } @Override public void save(final Session session) { - String id = session.id(); - Map attributes = session.attributes(); - - try(Transaction tx = db.beginTx()) { - Optional.ofNullable(db.findNode(Label.label(sessionLabel), "_id", id)) - .map(node -> { - node.setProperty("_accessedAt", session.accessedAt()); - node.setProperty("_createdAt", session.createdAt()); - node.setProperty("_savedAt", session.savedAt()); - - attributes.forEach((k ,v) -> node.setProperty(encode(k), v)); - - if (!node.hasProperty("_expire")) { - node.setProperty("_expire", System.currentTimeMillis() + timeout * 1000); - } - - return node; - }).orElseGet(() -> { - Node node = db.createNode(Label.label(sessionLabel)); - - node.setProperty("_accessedAt", session.accessedAt()); - node.setProperty("_createdAt", session.createdAt()); - node.setProperty("_savedAt", session.savedAt()); - - attributes.forEach((k, v) -> node.setProperty(encode(k), v)); - node.setProperty("_expire", System.currentTimeMillis() + timeout * 1000); - - return node; - }); - tx.success(); + String sid = session.id(); + Map attributes = new HashMap<>(session.attributes()); + + JcNode node = new JcNode("n"); + JcQuery query = new JcQuery(); + List clauses = new ArrayList<>(); + clauses.add(MATCH.node(node).label(label).property("_id").value(sid)); + attributes.put("_accessedAt", session.accessedAt()); + attributes.put("_createdAt", session.createdAt()); + attributes.put("_savedAt", session.savedAt()); + attributes.put("_expire", expire.getAsLong()); + attributes.forEach((k, v) -> { + clauses.add(DO.SET(node.property(k)).to(v)); + }); + clauses.add(RETURN.value(node)); + + query.setClauses(clauses.toArray(new IClause[clauses.size()])); + List nodes = db.execute(query).resultOf(node); + if (nodes.size() == 1) { + GrNode found = nodes.get(0); + Set keys = found.getProperties().stream() + .map(GrProperty::getName) + .collect(Collectors.toSet()); + keys.removeAll(attributes.keySet()); + // unset properties + if (keys.size() > 0) { + log.debug("removing {} => {}", sid, keys); + JcQuery unsetQuery = new JcQuery(); + List unsetClauses = new ArrayList<>(); + unsetClauses.add(MATCH.node(node).label(label).property("_id").value(sid)); + keys.forEach(key -> unsetClauses.add(DO.REMOVE(node.property(key)))); + unsetQuery.setClauses(unsetClauses.toArray(new IClause[unsetClauses.size()])); + db.execute(unsetQuery); + } + log.debug("saved {} => {}", sid, nodes); + } else { + // create session: + query = new JcQuery(); + Node create = CREATE.node(node).label(label); + create.property("_id").value(sid); + attributes.forEach((k, v) -> create.property(k).value(v)); + + query.setClauses(new IClause[]{create }); + List result = db.execute(query).resultOf(node); + log.debug("created {} => {}", sid, result); } } @Override - public void create(Session session) { + public void create(final Session session) { save(session); } @Override - public void delete(String id) { - try(Transaction tx = db.beginTx()) { - db.findNode(Label.label(sessionLabel), "_id", id).delete(); - tx.success(); - } + public void delete(final String id) { + JcNode session = new JcNode("n"); + JcQuery q = new JcQuery(); + q.setClauses(new IClause[]{ + MATCH.node(session).label(label).property("_id").value(id), + DO.DELETE(session) + }); + JcQueryResult rsp = db.execute(q); + log.debug("destroyed {} => {}", id, rsp); } private static long seconds(final String value) { @@ -213,43 +266,8 @@ private static long seconds(final String value) { return Long.parseLong(value); } catch (NumberFormatException ex) { Config config = ConfigFactory.empty() - .withValue("timeout", ConfigValueFactory.fromAnyRef(value)); + .withValue("timeout", ConfigValueFactory.fromAnyRef(value)); return config.getDuration("timeout", TimeUnit.SECONDS); - } catch (NullPointerException ex) { - return 1800L; - } - } - - private String encode(final String key) { - String value = key; - if (value.charAt(0) == DOLLAR) { - value = UDOLLAR + value.substring(1); - } - return value.replace(DOT, UDOT); - } - - private String decode(final String key) { - String value = key; - if (value.charAt(0) == UDOLLAR) { - value = DOLLAR + value.substring(1); - } - return value.replace(UDOT, DOT); - } - - private static Pair getOrCreateGraph(String databaseDir) { - if (dbService == null && graphRuntime == null) { - GraphDatabaseBuilder builder = new GraphDatabaseFactory().newEmbeddedDatabaseBuilder(new File(databaseDir)); - dbService = builder.newGraphDatabase(); - graphRuntime = GraphAwareRuntimeFactory.createRuntime(dbService); - - ExpirationConfiguration configuration = ExpirationConfiguration.defaultConfiguration().withNodeExpirationProperty("_expire"); - graphRuntime.registerModule(new ExpirationModule("EXP", dbService, configuration)); - - graphRuntime.start(); - graphRuntime.waitUntilStarted(); - - return Pair.of(dbService, graphRuntime); } - return Pair.of(dbService, graphRuntime); } -} \ No newline at end of file +} diff --git a/modules/jooby-neo4j/src/test/java/apps/embedded/neo4j/EmbeddedNeo4jApp.java b/modules/jooby-neo4j/src/test/java/apps/embedded/neo4j/EmbeddedNeo4jApp.java deleted file mode 100644 index c166af760f..0000000000 --- a/modules/jooby-neo4j/src/test/java/apps/embedded/neo4j/EmbeddedNeo4jApp.java +++ /dev/null @@ -1,35 +0,0 @@ -package apps.embedded.neo4j; - -import org.jooby.Jooby; -import org.jooby.Session; -import org.jooby.embedded.neo4j.EmbeddedNeo4j; -import org.jooby.embedded.neo4j.EmbeddedNeo4jSessionStore; - -import java.util.concurrent.atomic.AtomicInteger; - -public class EmbeddedNeo4jApp extends Jooby { - - { - - conf("embedded/application.conf"); - - use(new EmbeddedNeo4j()); - - AtomicInteger inc = new AtomicInteger(0); - session(EmbeddedNeo4jSessionStore.class); - - get("/", req -> { - Session session = req.ifSession().orElseGet(() -> { - Session newSession = req.session(); - int next = newSession.get("inc").intValue(inc.getAndIncrement()); - newSession.set("inc", next); - return newSession; - }); - return session.get("inc"); - }); - } - - public static void main(String[] args) { - run(EmbeddedNeo4jApp::new, args); - } -} \ No newline at end of file diff --git a/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jBoltApp.java b/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jBoltApp.java new file mode 100644 index 0000000000..cac86b666e --- /dev/null +++ b/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jBoltApp.java @@ -0,0 +1,30 @@ +package apps.neo4j; + +import org.jooby.Jooby; +import org.jooby.neo4j.Neo4j; + +import com.typesafe.config.ConfigFactory; +import com.typesafe.config.ConfigValueFactory; + +import iot.jcypher.database.remote.BoltDBAccess; + +public class Neo4jBoltApp extends Jooby { + + { + use(ConfigFactory.empty() + .withValue("db.url", ConfigValueFactory.fromAnyRef("bolt://localhost:7687")) + .withValue("db.user", ConfigValueFactory.fromAnyRef("neo4j")) + .withValue("db.password", ConfigValueFactory.fromAnyRef("development"))); + + use(new Neo4j()); + + onStart(() -> { + System.out.println(require(BoltDBAccess.class).getSession()); + }); + + } + + public static void main(final String[] args) { + run(Neo4jBoltApp::new, args); + } +} diff --git a/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jBoltSessionApp.java b/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jBoltSessionApp.java new file mode 100644 index 0000000000..d40739c4a9 --- /dev/null +++ b/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jBoltSessionApp.java @@ -0,0 +1,57 @@ +package apps.neo4j; + +import org.jooby.Jooby; +import org.jooby.Session; +import org.jooby.neo4j.Neo4j; +import org.jooby.neo4j.Neo4jSessionStore; + +import com.typesafe.config.ConfigFactory; +import com.typesafe.config.ConfigValueFactory; + +import iot.jcypher.database.IDBAccess; + +public class Neo4jBoltSessionApp extends Jooby { + + { + use(ConfigFactory.empty() + .withValue("db.url", ConfigValueFactory.fromAnyRef("bolt://localhost:7687")) + .withValue("db.user", ConfigValueFactory.fromAnyRef("neo4j")) + .withValue("db.password", ConfigValueFactory.fromAnyRef("development"))); + + use(new Neo4j()); + + session(Neo4jSessionStore.class); + + get("/set", req -> { + String attr = req.param("attr").value("foo"); + Session session = req.session(); + session.set("attr", attr); + return attr; + }); + + get("/get", req -> { + Session session = req.session(); + return session.get("attr").value(); + }); + + get("/remove", req -> { + Session session = req.session(); + return session.unset("attr").value(); + }); + + get("/clear", req -> { + IDBAccess db = require(IDBAccess.class); + db.clearDatabase(); + return "done"; + }); + + get("/destroy", req -> { + req.session().destroy(); + return "destroy"; + }); + } + + public static void main(final String[] args) { + run(Neo4jBoltSessionApp::new, args); + } +} diff --git a/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jFsApp.java b/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jFsApp.java new file mode 100644 index 0000000000..919ae6a866 --- /dev/null +++ b/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jFsApp.java @@ -0,0 +1,28 @@ +package apps.neo4j; + +import org.jooby.Jooby; +import org.jooby.neo4j.Neo4j; +import org.neo4j.graphdb.GraphDatabaseService; + +import com.typesafe.config.ConfigFactory; +import com.typesafe.config.ConfigValueFactory; + +public class Neo4jFsApp extends Jooby { + + { + use(ConfigFactory.empty() + .withValue("com.graphaware.runtime.enabled", ConfigValueFactory.fromAnyRef(true)) + .withValue("db", ConfigValueFactory.fromAnyRef("fs"))); + + use(new Neo4j("target/localdb")); + + onStart(() -> { + System.out.println(require(GraphDatabaseService.class)); + }); + + } + + public static void main(final String[] args) { + run(Neo4jFsApp::new, args); + } +} diff --git a/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jFsApp2.java b/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jFsApp2.java new file mode 100644 index 0000000000..a7f75fc613 --- /dev/null +++ b/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jFsApp2.java @@ -0,0 +1,21 @@ +package apps.neo4j; + +import org.jooby.Jooby; +import org.jooby.neo4j.Neo4j; +import org.neo4j.graphdb.GraphDatabaseService; + +public class Neo4jFsApp2 extends Jooby { + + { + use(new Neo4j("fs")); + + onStart(() -> { + System.out.println(require(GraphDatabaseService.class)); + }); + + } + + public static void main(final String[] args) { + run(Neo4jFsApp2::new, args); + } +} diff --git a/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jInMemoryApp.java b/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jInMemoryApp.java new file mode 100644 index 0000000000..6e12d46875 --- /dev/null +++ b/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jInMemoryApp.java @@ -0,0 +1,26 @@ +package apps.neo4j; + +import org.jooby.Jooby; +import org.jooby.neo4j.Neo4j; +import org.neo4j.graphdb.GraphDatabaseService; + +import com.typesafe.config.ConfigFactory; +import com.typesafe.config.ConfigValueFactory; + +public class Neo4jInMemoryApp extends Jooby { + + { + use(ConfigFactory.empty() + .withValue("db", ConfigValueFactory.fromAnyRef("mem"))); + use(new Neo4j()); + + onStart(() -> { + System.out.println(require(GraphDatabaseService.class)); + }); + + } + + public static void main(final String[] args) { + run(Neo4jInMemoryApp::new, args); + } +} diff --git a/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jInMemoryApp2.java b/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jInMemoryApp2.java new file mode 100644 index 0000000000..f6ffd1aba4 --- /dev/null +++ b/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jInMemoryApp2.java @@ -0,0 +1,21 @@ +package apps.neo4j; + +import org.jooby.Jooby; +import org.jooby.neo4j.Neo4j; +import org.neo4j.graphdb.GraphDatabaseService; + +public class Neo4jInMemoryApp2 extends Jooby { + + { + use(new Neo4j("mem")); + + onStart(() -> { + System.out.println(require(GraphDatabaseService.class)); + }); + + } + + public static void main(final String[] args) { + run(Neo4jInMemoryApp2::new, args); + } +} diff --git a/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jSessionApp.java b/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jSessionApp.java new file mode 100644 index 0000000000..9f0c763b28 --- /dev/null +++ b/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jSessionApp.java @@ -0,0 +1,61 @@ +package apps.neo4j; + +import org.jooby.Jooby; +import org.jooby.Session; +import org.jooby.neo4j.Neo4j; +import org.jooby.neo4j.Neo4jSessionStore; + +import com.google.common.collect.ImmutableMap; +import com.graphaware.neo4j.expire.ExpirationModuleBootstrapper; +import com.typesafe.config.ConfigFactory; +import com.typesafe.config.ConfigValueFactory; + +import iot.jcypher.database.IDBAccess; + +public class Neo4jSessionApp extends Jooby { + + { + use(ConfigFactory.empty() + .withValue("session.timeout", ConfigValueFactory.fromAnyRef(30)) + .withValue("com.graphaware.runtime.enabled", ConfigValueFactory.fromAnyRef(true)) + .withValue("com.graphaware.module", ConfigValueFactory.fromAnyRef( + (ImmutableMap.of("class", ExpirationModuleBootstrapper.class.getName(), + "nodeExpirationProperty", "_expire"))))); + + use(new Neo4j("mem")); + + session(Neo4jSessionStore.class); + + get("/set", req -> { + String attr = req.param("attr").value("foo"); + Session session = req.session(); + session.set("attr", attr); + return attr; + }); + + get("/get", req -> { + Session session = req.session(); + return session.get("attr").value(); + }); + + get("/unset", req -> { + Session session = req.session(); + return session.unset("attr").value(); + }); + + get("/clear", req -> { + IDBAccess db = require(IDBAccess.class); + db.clearDatabase(); + return "done"; + }); + + get("/destroy", req -> { + req.session().destroy(); + return "destroy"; + }); + } + + public static void main(final String[] args) { + run(Neo4jSessionApp::new, args); + } +} diff --git a/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jWrongApp.java b/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jWrongApp.java new file mode 100644 index 0000000000..0eafcb9c6a --- /dev/null +++ b/modules/jooby-neo4j/src/test/java/apps/neo4j/Neo4jWrongApp.java @@ -0,0 +1,15 @@ +package apps.neo4j; + +import org.jooby.Jooby; +import org.jooby.neo4j.Neo4j; + +public class Neo4jWrongApp extends Jooby { + + { + use(new Neo4j()); + } + + public static void main(final String[] args) { + run(Neo4jWrongApp::new, args); + } +} diff --git a/modules/jooby-neo4j/src/test/java/org/jooby/embedded/neo4j/EmbeddedNeo4jSessionStoreTest.java b/modules/jooby-neo4j/src/test/java/org/jooby/embedded/neo4j/EmbeddedNeo4jSessionStoreTest.java deleted file mode 100644 index ce8d2dd520..0000000000 --- a/modules/jooby-neo4j/src/test/java/org/jooby/embedded/neo4j/EmbeddedNeo4jSessionStoreTest.java +++ /dev/null @@ -1,242 +0,0 @@ -package org.jooby.embedded.neo4j; - -import com.google.common.collect.ImmutableMap; -import com.graphaware.neo4j.expire.ExpirationModule; -import com.graphaware.runtime.GraphAwareRuntime; -import org.jooby.Session; -import org.jooby.test.MockUnit; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.neo4j.graphdb.GraphDatabaseService; -import org.neo4j.graphdb.Label; -import org.neo4j.graphdb.Node; -import org.neo4j.graphdb.Transaction; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.function.BiConsumer; - -import static org.easymock.EasyMock.expect; -import static org.easymock.EasyMock.expectLastCall; -import static org.easymock.EasyMock.isA; -import static org.junit.Assert.assertEquals; - -@RunWith(PowerMockRunner.class) -@PrepareForTest({EmbeddedNeo4jSessionStore.class, LinkedHashMap.class, ExpirationModule.class}) -public class EmbeddedNeo4jSessionStoreTest { - private static GraphDatabaseService dbService; - private static GraphAwareRuntime graphAwareRuntime; - private static Session.Builder sb; - - @SuppressWarnings("unchecked") - MockUnit.Block boot = unit -> { - dbService = unit.get(GraphDatabaseService.class); - graphAwareRuntime = unit.get(GraphAwareRuntime.class); - ExpirationModule expirationModule = unit.get(ExpirationModule.class); - expect(graphAwareRuntime.getModule(isA(ExpirationModule.class.getClass()))).andReturn(expirationModule); - graphAwareRuntime.start(); - expectLastCall(); - graphAwareRuntime.waitUntilStarted(); - expectLastCall(); - }; - - long now = System.currentTimeMillis(); - - Map attrs = ImmutableMap.of("k.v", "v", "$d", "d"); - Map properties; - - MockUnit.Block saveSession = unit -> { - - Session session = unit.get(Session.class); - expect(session.id()).andReturn("1234"); - expect(session.accessedAt()).andReturn(now); - expect(session.createdAt()).andReturn(now); - expect(session.savedAt()).andReturn(now); - expect(session.attributes()).andReturn(attrs); - - Node node = unit.mock(Node.class); - node.setProperty(isA(String.class), isA(Object.class)); - expectLastCall().anyTimes(); - expect(node.hasProperty(isA(String.class))).andReturn(false); - expect(dbService.findNode(isA(Label.class), isA(String.class), isA(Object.class))).andReturn(node); - }; - - MockUnit.Block updateSession = unit -> { - - Session session = unit.get(Session.class); - expect(session.id()).andReturn("1234"); - expect(session.accessedAt()).andReturn(now); - expect(session.createdAt()).andReturn(now); - expect(session.savedAt()).andReturn(now); - expect(session.attributes()).andReturn(attrs); - - Node node = unit.mock(Node.class); - node.setProperty(isA(String.class), isA(Object.class)); - expectLastCall().anyTimes(); - expect(dbService.findNode(isA(Label.class), isA(String.class), isA(Object.class))).andReturn(null); - expect(dbService.createNode(isA(Label.class))).andReturn(node); - }; - - @SuppressWarnings("unchecked") - MockUnit.Block getFromSession = unit -> { - long now = System.currentTimeMillis(); - - Node node = unit.mock(Node.class); - expect(node.getAllProperties()).andReturn(properties); - - Map sessionMap = unit.constructor(LinkedHashMap.class) - .args(Map.class).build(properties); - - expect(sessionMap.remove("_accessedAt")).andReturn(now); - expect(sessionMap.remove("_createdAt")).andReturn(now); - expect(sessionMap.remove("_savedAt")).andReturn(now); - expect(sessionMap.remove("_id")).andReturn(now); - expect(sessionMap.remove("_expire")).andReturn(null); - sessionMap.forEach(unit.capture(BiConsumer.class)); - - sb = unit.mock(Session.Builder.class); - expect(sb.sessionId()).andReturn("1234"); - expect(sb.accessedAt(now)).andReturn(sb); - expect(sb.createdAt(now)).andReturn(sb); - expect(sb.savedAt(now)).andReturn(sb); - - Map.Entry property = properties.entrySet().iterator().next(); - expect(sb.set(property.getKey(), property.getValue().toString())).andReturn(sb); - - expect(sb.build()).andReturn(unit.get(Session.class)); - expect(dbService.findNode(isA(Label.class), isA(String.class), isA(Object.class))).andReturn(node); - }; - - MockUnit.Block handleTransaction = unit -> { - Transaction t = unit.mock(Transaction.class); - t.success(); - expectLastCall(); - t.close(); - expectLastCall(); - expect(dbService.beginTx()).andReturn(t); - }; - - @Test - public void defaults() throws Exception { - new MockUnit(GraphDatabaseService.class, GraphAwareRuntime.class, ExpirationModule.class) - .expect(boot) - .run(unit -> { - new EmbeddedNeo4jSessionStore(dbService, graphAwareRuntime, "sess", 60L); - }); - } - - @Test(expected = NullPointerException.class) - public void defaultsNullDB() throws Exception { - new EmbeddedNeo4jSessionStore(null, null, "sess", 60L); - } - - @Test - public void create() throws Exception { - new MockUnit(Session.class, GraphDatabaseService.class, GraphAwareRuntime.class, ExpirationModule.class) - .expect(boot) - .expect(saveSession) - .expect(handleTransaction) - .run(unit -> { - new EmbeddedNeo4jSessionStore(dbService, graphAwareRuntime, "sess", 60L) - .create(unit.get(Session.class)); - }); - } - - @Test - public void save() throws Exception { - new MockUnit(Session.class, GraphDatabaseService.class, GraphAwareRuntime.class, ExpirationModule.class) - .expect(boot) - .expect(saveSession) - .expect(handleTransaction) - .run(unit -> { - new EmbeddedNeo4jSessionStore(dbService, graphAwareRuntime, "sess", 60L) - .save(unit.get(Session.class)); - }); - } - - @Test - public void update() throws Exception { - new MockUnit(Session.class, GraphDatabaseService.class, GraphAwareRuntime.class, ExpirationModule.class) - .expect(boot) - .expect(updateSession) - .expect(handleTransaction) - .run(unit -> { - new EmbeddedNeo4jSessionStore(dbService, graphAwareRuntime, "sess", 60L) - .save(unit.get(Session.class)); - }); - } - - @SuppressWarnings("unchecked") - @Test - public void get() throws Exception { - properties = Collections.singletonMap("a.b", "c"); - new MockUnit(Session.class, GraphDatabaseService.class, GraphAwareRuntime.class, ExpirationModule.class) - .expect(boot) - .expect(getFromSession) - .expect(handleTransaction) - .run(unit -> { - EmbeddedNeo4jSessionStore nss = new EmbeddedNeo4jSessionStore(dbService, graphAwareRuntime, "sess", 60L); - assertEquals(unit.get(Session.class), nss.get(sb)); - }, unit -> { - BiConsumer setter = unit.captured(BiConsumer.class).get(0); - setter.accept("a\uFF0Eb", "c"); - }); - } - - @SuppressWarnings("unchecked") - @Test - public void getDollar() throws Exception { - properties = Collections.singletonMap("$ab", "c"); - new MockUnit(Session.class, GraphDatabaseService.class, GraphAwareRuntime.class, ExpirationModule.class) - .expect(boot) - .expect(getFromSession) - .expect(handleTransaction) - .run(unit -> { - EmbeddedNeo4jSessionStore nss = new EmbeddedNeo4jSessionStore(dbService, graphAwareRuntime, "sess", 60L); - assertEquals(unit.get(Session.class), nss.get(sb)); - }, unit -> { - BiConsumer setter = unit.captured(BiConsumer.class).get(0); - setter.accept("\uFF04ab", "c"); - }); - } - - @Test - public void getExpired() throws Exception { - new MockUnit(Session.class, GraphDatabaseService.class, GraphAwareRuntime.class, ExpirationModule.class) - .expect(boot) - .expect(unit -> { - sb = unit.mock(Session.Builder.class); - expect(sb.sessionId()).andReturn("1234"); - expect(dbService.findNode(isA(Label.class), isA(String.class), isA(Object.class))).andReturn(null); - }) - .expect(unit -> { - Transaction t = unit.mock(Transaction.class); - t.close(); - expectLastCall(); - expect(dbService.beginTx()).andReturn(t); - }) - .run(unit -> { - EmbeddedNeo4jSessionStore nss = new EmbeddedNeo4jSessionStore(dbService, graphAwareRuntime, "sess", 60L); - assertEquals(null, nss.get(sb)); - }); - } - - @Test - public void delete() throws Exception { - new MockUnit(Session.class, GraphDatabaseService.class, GraphAwareRuntime.class, ExpirationModule.class) - .expect(boot) - .expect(unit -> { - Node node = unit.mock(Node.class); - node.delete(); - expectLastCall(); - expect(dbService.findNode(isA(Label.class), isA(String.class), isA(Object.class))).andReturn(node); - }) - .expect(handleTransaction) - .run(unit -> { - new EmbeddedNeo4jSessionStore(dbService, graphAwareRuntime, "sess", 60L).delete("1234"); - }); - } -} \ No newline at end of file diff --git a/modules/jooby-neo4j/src/test/java/org/jooby/embedded/neo4j/EmbeddedNeo4jTest.java b/modules/jooby-neo4j/src/test/java/org/jooby/embedded/neo4j/EmbeddedNeo4jTest.java deleted file mode 100644 index 3a8bdf20cf..0000000000 --- a/modules/jooby-neo4j/src/test/java/org/jooby/embedded/neo4j/EmbeddedNeo4jTest.java +++ /dev/null @@ -1,183 +0,0 @@ -package org.jooby.embedded.neo4j; - -import com.google.inject.Binder; -import com.google.inject.Key; -import com.google.inject.binder.AnnotatedBindingBuilder; -import com.google.inject.name.Names; -import com.graphaware.runtime.GraphAwareRuntime; -import com.graphaware.runtime.GraphAwareRuntimeFactory; -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; -import com.typesafe.config.ConfigValueFactory; -import javaslang.control.Try; -import org.jooby.Env; -import org.jooby.test.MockUnit; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.neo4j.graphdb.GraphDatabaseService; -import org.neo4j.graphdb.factory.GraphDatabaseBuilder; -import org.neo4j.graphdb.factory.GraphDatabaseFactory; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -import java.io.File; - -import static org.easymock.EasyMock.expect; -import static org.easymock.EasyMock.expectLastCall; -import static org.easymock.EasyMock.isA; -import static org.junit.Assert.assertEquals; - -@RunWith(PowerMockRunner.class) -@PrepareForTest({GraphDatabaseService.class, GraphAwareRuntime.class, - GraphAwareRuntimeFactory.class, GraphDatabaseFactory.class, - GraphDatabaseBuilder.class, EmbeddedNeo4j.class}) -public class EmbeddedNeo4jTest { - - private Config $neo4j = ConfigFactory.parseResources(getClass(), "/embedded/embedded_neo4j.conf"); - private GraphDatabaseService dbService; - private GraphAwareRuntime graphRuntime; - - @SuppressWarnings("unchecked") - MockUnit.Block neo4j = unit -> { - dbService = unit.mock(GraphDatabaseService.class); - unit.registerMock(GraphDatabaseService.class, dbService); - dbService.shutdown(); - expectLastCall(); - - GraphDatabaseBuilder graphDatabaseBuilder = unit.mock(GraphDatabaseBuilder.class); - unit.registerMock(GraphDatabaseBuilder.class, graphDatabaseBuilder); - expect(graphDatabaseBuilder.newGraphDatabase()).andReturn(dbService); - - GraphDatabaseFactory graphDatabaseFactory = unit.mockConstructor(GraphDatabaseFactory.class); - expect(graphDatabaseFactory.newEmbeddedDatabaseBuilder(isA(File.class))).andReturn(graphDatabaseBuilder); - unit.registerMock(GraphDatabaseFactory.class, graphDatabaseFactory); - - graphRuntime = unit.mock(GraphAwareRuntime.class); - unit.registerMock(GraphAwareRuntime.class, graphRuntime); - - unit.mockStatic(GraphAwareRuntimeFactory.class); - expect(GraphAwareRuntimeFactory.createRuntime(dbService)).andReturn(graphRuntime); - - AnnotatedBindingBuilder dbsABB = unit.mock(AnnotatedBindingBuilder.class); - dbsABB.toInstance(dbService); - dbsABB.toInstance(dbService); - - AnnotatedBindingBuilder grABB = unit.mock(AnnotatedBindingBuilder.class); - grABB.toInstance(graphRuntime); - grABB.toInstance(graphRuntime); - - Binder binder = unit.get(Binder.class); - expect(binder.bind(Key.get(GraphDatabaseService.class))).andReturn(dbsABB); - expect(binder.bind(Key.get(GraphDatabaseService.class, Names.named("/tmp")))).andReturn(dbsABB); - - expect(binder.bind(Key.get(GraphAwareRuntime.class))).andReturn(grABB); - expect(binder.bind(Key.get(GraphAwareRuntime.class, Names.named("/tmp")))).andReturn(grABB); - - Env env = unit.get(Env.class); - expect(env.onStop(unit.capture(Try.CheckedRunnable.class))).andReturn(env); - }; - - @Test - public void defaults() throws Exception { - new MockUnit(Config.class, Binder.class, Env.class) - .expect(unit -> { - Config config = unit.get(Config.class); - expect(config.getConfig("neo4j")).andReturn($neo4j.getConfig("neo4j")); - expect(config.hasPath("neo4j")).andReturn(false); - }) - .expect(serviceKey(new Env.ServiceKey())) - .expect(neo4j) - .expect(unit -> { - expect(dbService.isAvailable(60000L)).andReturn(true); - }) - .run(unit -> { - EmbeddedNeo4j embeddedNeo4j = new EmbeddedNeo4j(); - embeddedNeo4j.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); - }, unit -> { - unit.captured(Try.CheckedRunnable.class).iterator().next().run(); - }); - } - - @Test(expected = IllegalArgumentException.class) - public void shouldFailWhenDbIsMissing() throws Exception { - new MockUnit(Config.class, Binder.class, Env.class) - .expect(unit -> { - Config config = unit.get(Config.class); - expect(config.getConfig("neo4j")).andReturn($neo4j.getConfig("neo4j")); - expect(config.hasPath("neo4j")).andReturn(false); - }) - .expect(neo4j) - .expect(unit -> { - expect(dbService.isAvailable(60000L)).andReturn(false); - }) - .run(unit -> { - EmbeddedNeo4j embeddedNeo4j = new EmbeddedNeo4j(); - embeddedNeo4j.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); - }); - } - - @Test - public void defaultsWithCustomAction() throws Exception { - new MockUnit(Config.class, Binder.class, Env.class) - .expect(unit -> { - Config config = unit.get(Config.class); - expect(config.getConfig("neo4j")).andReturn($neo4j.getConfig("neo4j")); - expect(config.hasPath("neo4j")).andReturn(true); - expect(config.getConfig("neo4j")).andReturn(ConfigFactory.empty() - .withValue("databaseDir", ConfigValueFactory.fromAnyRef("/tmp"))); - }) - .expect(serviceKey(new Env.ServiceKey())) - .expect(neo4j) - .expect(unit -> { - expect(dbService.isAvailable(60000L)).andReturn(true); - }) - .run(unit -> { - new EmbeddedNeo4j() - .properties((properties, config) -> { - assertEquals("/tmp", properties.getProperty("databaseDir")); - }) - .configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); - }, unit -> { - unit.captured(Try.CheckedRunnable.class).iterator().next().run(); - }); - } - - @Test - public void defaultsConfig() throws Exception { - new MockUnit(Config.class, Binder.class, Env.class) - .expect(unit -> { - assertEquals($neo4j, new EmbeddedNeo4j().config()); - }); - } - - @Test - public void defaultsWithProperties() throws Exception { - new MockUnit(Config.class, Binder.class, Env.class) - .expect(unit -> { - Config config = unit.get(Config.class); - expect(config.getConfig("neo4j")).andReturn($neo4j.getConfig("neo4j")); - expect(config.hasPath("neo4j")).andReturn(false); - }) - .expect(serviceKey(new Env.ServiceKey())) - .expect(neo4j) - .expect(unit -> { - expect(dbService.isAvailable(60000L)).andReturn(true); - }) - .run(unit -> { - new EmbeddedNeo4j() - .properties((properties, config) -> { - properties.put("databaseDir", "/tmp"); - }) - .configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); - }, unit -> { - unit.captured(Try.CheckedRunnable.class).iterator().next().run(); - }); - } - - private MockUnit.Block serviceKey(final Env.ServiceKey serviceKey) { - return unit -> { - Env env = unit.get(Env.class); - expect(env.serviceKey()).andReturn(serviceKey); - }; - } -} \ No newline at end of file diff --git a/modules/jooby-neo4j/src/test/java/org/jooby/neo4j/Neo4jSessionStoreTest.java b/modules/jooby-neo4j/src/test/java/org/jooby/neo4j/Neo4jSessionStoreTest.java new file mode 100644 index 0000000000..aac6b45af7 --- /dev/null +++ b/modules/jooby-neo4j/src/test/java/org/jooby/neo4j/Neo4jSessionStoreTest.java @@ -0,0 +1,439 @@ +package org.jooby.neo4j; + +import static org.easymock.EasyMock.expect; +import static org.junit.Assert.assertEquals; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.jooby.Session; +import org.jooby.Session.Builder; +import org.jooby.test.MockUnit; +import org.jooby.test.MockUnit.Block; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import iot.jcypher.database.IDBAccess; +import iot.jcypher.graph.GrNode; +import iot.jcypher.graph.GrProperty; +import iot.jcypher.query.JcQuery; +import iot.jcypher.query.JcQueryResult; +import iot.jcypher.query.api.IClause; +import iot.jcypher.query.api.modify.ModifyTerminal; +import iot.jcypher.query.api.modify.Set; +import iot.jcypher.query.api.pattern.Node; +import iot.jcypher.query.api.pattern.Property; +import iot.jcypher.query.api.returns.RSortable; +import iot.jcypher.query.factories.clause.CREATE; +import iot.jcypher.query.factories.clause.DO; +import iot.jcypher.query.factories.clause.MATCH; +import iot.jcypher.query.factories.clause.RETURN; +import iot.jcypher.query.values.JcNode; +import iot.jcypher.query.values.JcProperty; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({Neo4jSessionStore.class, JcQuery.class, JcNode.class, MATCH.class, DO.class, + System.class, RETURN.class, CREATE.class }) +public class Neo4jSessionStoreTest { + + @Test + public void newStore() throws Exception { + new MockUnit(IDBAccess.class) + .run(unit -> { + new Neo4jSessionStore(unit.get(IDBAccess.class), "session", "30m"); + }); + } + + @Test + public void get() throws Exception { + new MockUnit(IDBAccess.class, Session.Builder.class) + .expect(sessionId("sidfoo")) + .expect(newNode("n")) + .expect(millis()) + .expect(GET("sidfoo", "session", TimeUnit.MINUTES.toMillis(30))) + .expect(newQuery()) + .expect(execute()) + .expect(resultOf()) + .expect(newSession()) + .run(unit -> { + Session session = new Neo4jSessionStore(unit.get(IDBAccess.class), "session", "30m") + .get(unit.get(Session.Builder.class)); + assertEquals(unit.get(Session.class), session); + }); + } + + @Test + public void getNull() throws Exception { + new MockUnit(IDBAccess.class, Session.Builder.class) + .expect(sessionId("sidfoo")) + .expect(newNode("n")) + .expect(millis()) + .expect(GET("sidfoo", "session", TimeUnit.SECONDS.toMillis(30))) + .expect(newQuery()) + .expect(execute()) + .expect(emptyResultOf()) + .run(unit -> { + Session session = new Neo4jSessionStore(unit.get(IDBAccess.class), "session", "30") + .get(unit.get(Session.Builder.class)); + assertEquals(null, session); + }); + } + + @Test + public void save() throws Exception { + new MockUnit(IDBAccess.class, Session.class) + .expect(sid("sidfoo", ImmutableMap.of("foo", "bar"), 1L, 2L, 3L)) + .expect(newNode("n")) + .expect(millis()) + .expect(SAVE("sidfoo", "session", 1L, 2L, 3L, TimeUnit.MINUTES.toMillis(30))) + .expect(newQuery()) + .expect(execute()) + .expect(resultOf()) + .expect(unit -> { + GrProperty foo = unit.mock(GrProperty.class); + expect(foo.getName()).andReturn("foo"); + GrNode node = unit.get(GrNode.class); + expect(node.getProperties()).andReturn(ImmutableList.of(foo)); + }) + .run(unit -> { + new Neo4jSessionStore(unit.get(IDBAccess.class), "session", "30m") + .save(unit.get(Session.class)); + }); + } + + @Test + public void saveDeleteUnsetProps() throws Exception { + new MockUnit(IDBAccess.class, Session.class) + .expect(sid("sidfoo", ImmutableMap.of("foo", "bar"), 1L, 2L, 3L)) + .expect(newNode("n")) + .expect(millis()) + .expect(SAVE("sidfoo", "session", 1L, 2L, 3L, TimeUnit.MINUTES.toMillis(30))) + .expect(newQuery()) + .expect(execute()) + .expect(resultOf()) + .expect(unit -> { + GrProperty bar = unit.mock(GrProperty.class); + expect(bar.getName()).andReturn("bar"); + GrNode node = unit.get(GrNode.class); + expect(node.getProperties()).andReturn(ImmutableList.of(bar)); + }) + .expect(newQuery()) + .expect(REMOVE("sidfoo", "session", "bar")) + .expect(execute()) + .run(unit -> { + new Neo4jSessionStore(unit.get(IDBAccess.class), "session", "30m") + .save(unit.get(Session.class)); + }); + } + + @Test + public void create() throws Exception { + new MockUnit(IDBAccess.class, Session.class) + .expect(sid("sidfoo", ImmutableMap.of("foo", "bar"), 1L, 2L, 3L)) + .expect(newNode("n")) + .expect(millis()) + .expect(SAVE("sidfoo", "session", 1L, 2L, 3L, TimeUnit.MINUTES.toMillis(30))) + .expect(newQuery()) + .expect(execute()) + .expect(emptyResultOf()) + .expect(newQuery()) + .expect(CREATE("sidfoo", "session", 1L, 2L, 3L, TimeUnit.MINUTES.toMillis(30))) + .expect(execute()) + .expect(emptyResultOf()) + .run(unit -> { + new Neo4jSessionStore(unit.get(IDBAccess.class), "session", "30m") + .create(unit.get(Session.class)); + }); + } + + @SuppressWarnings("unchecked") + @Test + public void delete() throws Exception { + new MockUnit(IDBAccess.class) + .expect(newNode("n")) + .expect(unit -> { + { + unit.mockStatic(MATCH.class); + + Node node = unit.mock(Node.class); + + Property prop = unit.mock(Property.class); + expect(prop.value("sidfoo")).andReturn(node); + + expect(node.label("session")).andReturn(node); + expect(node.property("_id")).andReturn(prop); + expect(MATCH.node(unit.get(JcNode.class))).andReturn(node); + } + + { + unit.mockStatic(DO.class); + JcNode node = unit.get(JcNode.class); + expect(DO.DELETE(node)).andReturn(null); + } + }) + .expect(newQuery()) + .expect(execute()) + .run(unit -> { + new Neo4jSessionStore(unit.get(IDBAccess.class), "session", "30m") + .delete("sidfoo"); + }); + } + + @SuppressWarnings("unchecked") + private Block CREATE(final String sid, final String label, final long accessedAt, + final long createdAt, final long savedAt, final long millis) { + return unit -> { + { + unit.mockStatic(CREATE.class); + + Node node = unit.mock(Node.class); + + Property id = unit.mock(Property.class); + expect(id.value(sid)).andReturn(node); + + Property _accessedAt = unit.mock(Property.class); + expect(_accessedAt.value(accessedAt)).andReturn(node); + + Property _savedAt = unit.mock(Property.class); + expect(_savedAt.value(savedAt)).andReturn(node); + + Property _createdAt = unit.mock(Property.class); + expect(_createdAt.value(createdAt)).andReturn(node); + + Property _expire = unit.mock(Property.class); + expect(_expire.value(millis)).andReturn(node); + + Property _foo = unit.mock(Property.class); + expect(_foo.value("bar")).andReturn(node); + + expect(node.label(label)).andReturn(node); + expect(node.property("_id")).andReturn(id); + expect(node.property("_accessedAt")).andReturn(_accessedAt); + expect(node.property("_savedAt")).andReturn(_savedAt); + expect(node.property("_createdAt")).andReturn(_createdAt); + expect(node.property("_expire")).andReturn(_expire); + expect(node.property("foo")).andReturn(_foo); + expect(CREATE.node(unit.get(JcNode.class))).andReturn(node); + } + }; + } + + @SuppressWarnings("unchecked") + private Block SAVE(final String sid, final String label, final long accessedAt, + final long createdAt, final long savedAt, final long timeout) { + return unit -> { + { + unit.mockStatic(MATCH.class); + + Node node = unit.mock(Node.class); + + Property prop = unit.mock(Property.class); + expect(prop.value(sid)).andReturn(node); + + expect(node.label(label)).andReturn(node); + expect(node.property("_id")).andReturn(prop); + expect(MATCH.node(unit.get(JcNode.class))).andReturn(node); + } + + DO_SET(unit, "_accessedAt", accessedAt); + DO_SET(unit, "_createdAt", createdAt); + DO_SET(unit, "_savedAt", savedAt); + DO_SET(unit, "_expire", timeout); + DO_SET(unit, "foo", "bar"); + }; + } + + @SuppressWarnings("unchecked") + private void DO_SET(final MockUnit unit, final String name, final Object value) { + unit.mockStatic(DO.class); + + JcProperty prop = unit.mock(JcProperty.class); + JcNode node = unit.get(JcNode.class); + expect(node.property(name)).andReturn(prop); + + Set mod = unit.mock(Set.class); + expect(mod.to(value)).andReturn(null); + + expect(DO.SET(prop)).andReturn(mod); + } + + @SuppressWarnings("unchecked") + private Block GET(final String sid, final String label, final long timeout) { + return unit -> { + { + unit.mockStatic(MATCH.class); + + Node node = unit.mock(Node.class); + + Property prop = unit.mock(Property.class); + expect(prop.value(sid)).andReturn(node); + + expect(node.label(label)).andReturn(node); + expect(node.property("_id")).andReturn(prop); + expect(MATCH.node(unit.get(JcNode.class))).andReturn(node); + } + + { + unit.mockStatic(DO.class); + + JcNode node = unit.get(JcNode.class); + + Set setprop = unit.mock(Set.class); + expect(setprop.to(timeout)) + .andReturn(unit.registerMock(ModifyTerminal.class)); + + JcProperty prop = unit.mock(JcProperty.class); + expect(node.property("_expire")).andReturn(prop); + expect(DO.SET(prop)).andReturn(setprop); + } + + { + unit.mockStatic(RETURN.class); + expect(RETURN.value(unit.get(JcNode.class))) + .andReturn(unit.registerMock(RSortable.class)); + } + }; + } + + @SuppressWarnings("unchecked") + private Block REMOVE(final String sid, final String label, final String property) { + return unit -> { + { + unit.mockStatic(MATCH.class); + + Node node = unit.mock(Node.class); + + Property prop = unit.mock(Property.class); + expect(prop.value(sid)).andReturn(node); + + expect(node.label(label)).andReturn(node); + expect(node.property("_id")).andReturn(prop); + expect(MATCH.node(unit.get(JcNode.class))).andReturn(node); + } + + { + unit.mockStatic(DO.class); + + JcProperty prop = unit.mock(JcProperty.class); + + JcNode node = unit.get(JcNode.class); + expect(node.property(property)).andReturn(prop); + + expect(DO.REMOVE(prop)).andReturn(unit.mock(ModifyTerminal.class)); + } + + { + unit.mockStatic(RETURN.class); + expect(RETURN.value(unit.get(JcNode.class))) + .andReturn(unit.registerMock(RSortable.class)); + } + }; + } + + private Block newSession() { + return unit -> { + Builder builder = unit.get(Session.Builder.class); + + GrProperty accessedAt = unit.mock(GrProperty.class); + expect(accessedAt.getValue()).andReturn(1L); + expect(builder.accessedAt(1L)).andReturn(builder); + + GrProperty createdAt = unit.mock(GrProperty.class); + expect(createdAt.getValue()).andReturn(2L); + expect(builder.createdAt(2L)).andReturn(builder); + + GrProperty savedAt = unit.mock(GrProperty.class); + expect(savedAt.getValue()).andReturn(3L); + expect(builder.savedAt(3L)).andReturn(builder); + + GrProperty foo = unit.mock(GrProperty.class); + expect(foo.getName()).andReturn("foo").times(2); + expect(foo.getValue()).andReturn("bar"); + expect(builder.set("foo", "bar")).andReturn(builder); + + GrProperty sid = unit.mock(GrProperty.class); + expect(sid.getName()).andReturn("_id"); + + GrNode node = unit.get(GrNode.class); + expect(node.getProperty("_accessedAt")).andReturn(accessedAt); + expect(node.getProperty("_createdAt")).andReturn(createdAt); + expect(node.getProperty("_savedAt")).andReturn(savedAt); + + expect(node.getProperties()).andReturn(ImmutableList.of(foo, sid)); + + expect(builder.build()).andReturn(unit.registerMock(Session.class)); + }; + } + + private Block resultOf() { + return unit -> { + GrNode node = unit.registerMock(GrNode.class); + JcQueryResult result = unit.get(JcQueryResult.class); + expect(result.resultOf(unit.get(JcNode.class))).andReturn(ImmutableList.of(node)); + }; + } + + private Block emptyResultOf() { + return unit -> { + JcQueryResult result = unit.get(JcQueryResult.class); + expect(result.resultOf(unit.get(JcNode.class))).andReturn(ImmutableList.of()); + }; + } + + private Block millis() { + return unit -> { + unit.mockStatic(System.class); + expect(System.currentTimeMillis()).andReturn(0L); + }; + } + + private Block newNode(final String name) { + return unit -> { + JcNode node = unit.constructor(JcNode.class) + .build(name); + unit.registerMock(JcNode.class, node); + }; + } + + private Block execute() { + return unit -> { + JcQueryResult result = unit.registerMock(JcQueryResult.class); + IDBAccess db = unit.get(IDBAccess.class); + expect(db.execute(unit.get(JcQuery.class))).andReturn(result); + }; + } + + private Block newQuery() { + return unit -> { + JcQuery query = unit.constructor(JcQuery.class) + .build(); + query.setClauses(unit.capture(IClause[].class)); + unit.registerMock(JcQuery.class, query); + }; + } + + private Block sessionId(final String sid) { + return unit -> { + Builder builder = unit.get(Session.Builder.class); + expect(builder.sessionId()).andReturn(sid); + }; + } + + private Block sid(final String sid, final Map attrs, final long accessedAt, + final long createdAt, final long savedAt) { + return unit -> { + Session session = unit.get(Session.class); + expect(session.id()).andReturn(sid); + expect(session.attributes()).andReturn(attrs); + + expect(session.accessedAt()).andReturn(accessedAt); + expect(session.createdAt()).andReturn(createdAt); + expect(session.savedAt()).andReturn(savedAt); + }; + } +} diff --git a/modules/jooby-neo4j/src/test/java/org/jooby/neo4j/Neo4jTest.java b/modules/jooby-neo4j/src/test/java/org/jooby/neo4j/Neo4jTest.java index 670599f896..d85b10085e 100644 --- a/modules/jooby-neo4j/src/test/java/org/jooby/neo4j/Neo4jTest.java +++ b/modules/jooby-neo4j/src/test/java/org/jooby/neo4j/Neo4jTest.java @@ -1,171 +1,586 @@ package org.jooby.neo4j; -import com.google.inject.Binder; -import com.google.inject.Key; -import com.google.inject.binder.AnnotatedBindingBuilder; -import com.google.inject.name.Names; -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; -import com.typesafe.config.ConfigValueFactory; -import iot.jcypher.database.DBAccessFactory; -import iot.jcypher.database.DBProperties; -import iot.jcypher.database.DBType; -import iot.jcypher.database.remote.BoltDBAccess; -import javaslang.control.Try; +import static org.easymock.EasyMock.eq; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.isA; +import static org.junit.Assert.assertEquals; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + import org.jooby.Env; +import org.jooby.Env.ServiceKey; import org.jooby.test.MockUnit; +import org.jooby.test.MockUnit.Block; import org.junit.Test; import org.junit.runner.RunWith; +import org.neo4j.driver.v1.AuthToken; +import org.neo4j.driver.v1.AuthTokens; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.GraphDatabase; import org.neo4j.driver.v1.Session; -import org.neo4j.driver.v1.exceptions.ClientException; -import org.powermock.api.easymock.PowerMock; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.factory.GraphDatabaseBuilder; +import org.neo4j.graphdb.factory.GraphDatabaseFactory; +import org.neo4j.logging.slf4j.Slf4jLogProvider; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import java.util.Properties; +import com.google.common.collect.ImmutableMap; +import com.google.inject.Binder; +import com.google.inject.Key; +import com.google.inject.binder.LinkedBindingBuilder; +import com.graphaware.neo4j.expire.ExpirationModuleBootstrapper; +import com.graphaware.runtime.RuntimeRegistry; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigException; +import com.typesafe.config.ConfigFactory; +import com.typesafe.config.ConfigValueFactory; -import static org.easymock.EasyMock.expect; -import static org.easymock.EasyMock.isA; -import static org.junit.Assert.assertEquals; -import static org.powermock.api.easymock.PowerMock.replayAll; +import iot.jcypher.database.DBAccessFactory; +import iot.jcypher.database.DBProperties; +import iot.jcypher.database.IDBAccess; +import iot.jcypher.database.embedded.EmbeddedDBAccess; +import iot.jcypher.database.remote.BoltDBAccess; +import javaslang.control.Try.CheckedRunnable; @RunWith(PowerMockRunner.class) -@PrepareForTest({BoltDBAccess.class, DBAccessFactory.class}) +@PrepareForTest({Neo4j.class, BoltDBAccess.class, DBAccessFactory.class, Properties.class, + GraphDatabaseFactory.class, RuntimeRegistry.class, EmbeddedDBAccess.class, GraphDatabase.class, + AuthTokens.class }) public class Neo4jTest { - private Config $neo4j = ConfigFactory.parseResources(getClass(), "/neo4j/neo4j.conf"); - private BoltDBAccess remoteClient; + @Test(expected = ConfigException.Missing.class) + public void missingDatabase() throws Exception { + new MockUnit(Env.class, Config.class, Binder.class) + .expect(hasPath("db.url", false)) + .expect(hasPath("db", false)) + .run(unit -> { + Neo4j neo4j = new Neo4j(); + neo4j.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); + }); + } - @SuppressWarnings("unchecked") - MockUnit.Block neo4j = unit -> { + @Test + public void config() throws Exception { + assertEquals(ConfigFactory.empty(getClass().getName().toLowerCase() + ".conf") + .withValue("neo4j.session.label", ConfigValueFactory.fromAnyRef("session")), + new Neo4j().config()); + } - remoteClient = unit.mock(BoltDBAccess.class); - unit.registerMock(BoltDBAccess.class, remoteClient); + @Test + public void rm() throws Exception { + Path dir = Paths.get("target", "foo"); + Neo4j.rm(dir); + Files.createDirectories(dir); + Files.createFile(dir.resolve("foo.txt")); + Path subdir = dir.resolve("bar"); + Files.createDirectories(subdir); + Files.createFile(subdir.resolve("bar.txt")); - AnnotatedBindingBuilder rcABB = unit.mock(AnnotatedBindingBuilder.class); - rcABB.toInstance(remoteClient); - rcABB.toInstance(remoteClient); + assertEquals(true, Files.exists(dir)); + Neo4j.rm(dir); + assertEquals(false, Files.exists(dir)); + Neo4j.rm(dir); + assertEquals(false, Files.exists(dir)); + } - Binder binder = unit.get(Binder.class); - expect(binder.bind(Key.get(BoltDBAccess.class))).andReturn(rcABB); - expect(binder.bind(Key.get(BoltDBAccess.class, Names.named("bolt://localhost:7687")))).andReturn(rcABB); + @Test + public void memDirect() throws Exception { + Path dir = Paths.get("target", "des").toAbsolutePath(); + Path dbdir = dir.resolve("neo4jmem").toAbsolutePath(); + new MockUnit(Env.class, Config.class, Binder.class) + .expect(hasPath("mem.url", false)) + .expect(hasPath("mem", false)) + .expect(hasPath("mem", false)) + .expect(hasPath("com.graphaware", false)) + .expect(hasPath("neo4j", false)) + .expect(hasPath("neo4j.mem", false)) + .expect(confString("application.tmpdir", dir.toString())) + .expect(props()) + .expect(setProp("database_dir", dbdir.toString())) + .expect(serviceKey()) + .expect(dbFactory(dbdir, "mem")) + .expect(embeddedAccess("mem")) + .expect(logdb(null, dbdir.toString())) + .expect(onStop()) + .run(unit -> { + Neo4j neo4j = new Neo4j("mem"); + neo4j.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); + }, closeOnStop(), + bind(GraphDatabaseService.class, 0), + bind(IDBAccess.class, 1), + unit -> { + IDBAccess db = unit.get(IDBAccess.class); + bind(db.getClass(), 2).run(unit); + }); + } - Env env = unit.get(Env.class); - expect(env.onStop(unit.capture(Try.CheckedRunnable.class))).andReturn(env); - }; + @SuppressWarnings({"unchecked", "rawtypes" }) + private Block bind(final Class class1, final int i) { + return unit -> { + List consumers = unit.captured(Consumer.class); + consumers.get(i).accept(Key.get(class1)); + }; + } @Test - public void defaults() throws Exception { + public void memAsProperty() throws Exception { + Path dir = Paths.get("target", "des").toAbsolutePath(); + Path dbdir = dir.resolve("neo4jmem").toAbsolutePath(); new MockUnit(Env.class, Config.class, Binder.class) - .expect(unit -> { - Config config = unit.get(Config.class); - expect(config.getConfig("neo4j")).andReturn($neo4j.getConfig("neo4j")); - expect(config.hasPath("neo4j.db")).andReturn(false); - }) - .expect(serviceKey(new Env.ServiceKey())) - .expect(neo4j) - .expect(unit -> { - PowerMock.mockStatic(DBAccessFactory.class); - expect(DBAccessFactory.createDBAccess(isA(DBType.class), isA(Properties.class), - isA(String.class), isA(String.class))).andReturn(remoteClient); - replayAll(); - expect(remoteClient.getSession()).andReturn(unit.registerMock(Session.class)); - }) - .expect(unit -> { - BoltDBAccess remoteClient = unit.get(BoltDBAccess.class); - remoteClient.close(); - }) - .run(unit -> { - Neo4j neo4j = new Neo4j(); - neo4j.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); - }, unit -> { - unit.captured(Try.CheckedRunnable.class).iterator().next().run(); - }); - } - - @Test(expected = ClientException.class) - public void shouldFailWhenDbIsMissing() throws Exception { + .expect(hasPath("db.url", false)) + .expect(hasPath("db", true)) + .expect(confString("db", "mem")) + .expect(hasPath("com.graphaware", false)) + .expect(hasPath("neo4j", false)) + .expect(hasPath("db", true)) + .expect(notAConf("db")) + .expect(hasPath("neo4j.db", false)) + .expect(confString("application.tmpdir", dir.toString())) + .expect(props()) + .expect(setProp("database_dir", dbdir.toString())) + .expect(serviceKey()) + .expect(dbFactory(dbdir, "db")) + .expect(embeddedAccess("db")) + .expect(logdb(null, dbdir.toString())) + .expect(onStop()) + .run(unit -> { + Neo4j neo4j = new Neo4j(); + neo4j.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); + }, closeOnStop(), + bind(GraphDatabaseService.class, 0), + bind(IDBAccess.class, 1), + unit -> { + IDBAccess db = unit.get(IDBAccess.class); + bind(db.getClass(), 2).run(unit); + }); + } + + @Test + public void bolt() throws Exception { new MockUnit(Env.class, Config.class, Binder.class) - .expect(unit -> { - Config config = unit.get(Config.class); - expect(config.getConfig("neo4j")).andReturn($neo4j.getConfig("neo4j")); - expect(config.hasPath("neo4j.db")).andReturn(false); - expect(config.getString("server_root_uri")).andReturn("bolt://localhost:7687"); - }) - .expect(neo4j) - .run(unit -> { - Neo4j neo4j = new Neo4j(); - neo4j.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); - }); + .expect(hasPath("db.url", true)) + .expect(confString("db.url", "bolt://localhost:7687")) + .expect(hasPath("com.graphaware", false)) + .expect(hasPath("neo4j", false)) + .expect(hasPath("db", true)) + .expect(confConf("db", ConfigFactory.empty())) + .expect(hasPath("neo4j.db", false)) + .expect(serviceKey()) + .expect(confString("db.user", "test")) + .expect(confString("db.password", "test")) + .expect(bolt("bolt://localhost:7687", "db", "test", "test")) + .expect(props()) + .expect(setProp("server_root_uri", "bolt://localhost:7687")) + .expect(boltAccess("db")) + .expect(logdb("bolt://localhost:7687", null)) + .expect(onStop()) + .run(unit -> { + Neo4j neo4j = new Neo4j(); + neo4j.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); + }, closeOnStop(), + bind(Driver.class, 0), + bind(Session.class, 1)); } @Test - public void defaultsWithCustomAction() throws Exception { + public void customConnection() throws Exception { new MockUnit(Env.class, Config.class, Binder.class) - .expect(unit -> { - Config config = unit.get(Config.class); - expect(config.getConfig("neo4j")) - .andReturn($neo4j.getConfig("neo4j")); - expect(config.hasPath("neo4j.db")).andReturn(true); - expect(config.getConfig("neo4j.db")).andReturn(ConfigFactory.empty() - .withValue("server_root_uri", ConfigValueFactory.fromAnyRef("bolt://localhost:7687"))); - }) - .expect(serviceKey(new Env.ServiceKey())) - .expect(neo4j) - .expect(unit -> { - PowerMock.mockStatic(DBAccessFactory.class); - expect(DBAccessFactory.createDBAccess(isA(DBType.class), isA(Properties.class), - isA(String.class), isA(String.class))).andReturn(remoteClient); - replayAll(); - expect(remoteClient.getSession()).andReturn(unit.registerMock(Session.class)); - }) - .run(unit -> { - new Neo4j() - .properties((properties, config) -> { - assertEquals("bolt://localhost:7687", properties.getProperty(DBProperties.SERVER_ROOT_URI)); - }) - .configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); - }); + .expect(hasPath("mydb.url", true)) + .expect(confString("mydb.url", "bolt://localhost:7687")) + .expect(hasPath("com.graphaware", false)) + .expect(hasPath("neo4j", false)) + .expect(hasPath("mydb", true)) + .expect(confConf("mydb", ConfigFactory.empty())) + .expect(hasPath("neo4j.mydb", false)) + .expect(serviceKey()) + .expect(confString("mydb.user", "test")) + .expect(confString("mydb.password", "test")) + .expect(bolt("bolt://localhost:7687", "mydb", "test", "test")) + .expect(props()) + .expect(setProp("server_root_uri", "bolt://localhost:7687")) + .expect(boltAccess("mydb")) + .expect(logdb("bolt://localhost:7687", null)) + .expect(onStop()) + .run(unit -> { + Neo4j neo4j = new Neo4j("mydb"); + neo4j.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); + }, closeOnStop(), + bind(Driver.class, 0), + bind(Session.class, 1)); } @Test - public void defaultsConfig() throws Exception { + public void fs() throws Exception { + Path dir = Paths.get("target", "des").toAbsolutePath(); + Path dbdir = dir.resolve("neo4jfs").toAbsolutePath(); new MockUnit(Env.class, Config.class, Binder.class) - .expect(unit -> { - assertEquals($neo4j, new Neo4j().config()); - }); + .expect(hasPath("db.url", false)) + .expect(hasPath("db", true)) + .expect(confString("db", "fs")) + .expect(hasPath("com.graphaware", false)) + .expect(hasPath("neo4j", false)) + .expect(hasPath("db", true)) + .expect(notAConf("db")) + .expect(hasPath("neo4j.db", false)) + .expect(confString("application.tmpdir", dir.toString())) + .expect(props()) + .expect(setProp("database_dir", dbdir.toString())) + .expect(serviceKey()) + .expect(dbFactory(dbdir, "db")) + .expect(embeddedAccess("db")) + .expect(logdb(null, dbdir.toString())) + .expect(onStop()) + .run(unit -> { + Neo4j neo4j = new Neo4j(); + neo4j.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); + }, closeOnStop(), + bind(GraphDatabaseService.class, 0), + bind(IDBAccess.class, 1), + unit -> { + IDBAccess db = unit.get(IDBAccess.class); + bind(db.getClass(), 2).run(unit); + }); } @Test - public void defaultsWithProperties() throws Exception { + public void fspath() throws Exception { + Path dir = Paths.get("target", "des").toAbsolutePath(); + Path dbdir = dir.resolve("dbdir").toAbsolutePath(); new MockUnit(Env.class, Config.class, Binder.class) - .expect(unit -> { - Config config = unit.get(Config.class); - expect(config.getConfig("neo4j")).andReturn($neo4j.getConfig("neo4j")); - expect(config.hasPath("neo4j.db")).andReturn(false); - }) - .expect(serviceKey(new Env.ServiceKey())) - .expect(neo4j) - .expect(unit -> { - PowerMock.mockStatic(DBAccessFactory.class); - expect(DBAccessFactory.createDBAccess(isA(DBType.class), isA(Properties.class), - isA(String.class), isA(String.class))).andReturn(remoteClient); - replayAll(); - expect(remoteClient.getSession()).andReturn(unit.registerMock(Session.class)); - }) - .run(unit -> { - new Neo4j() - .properties((properties, config) -> { - properties.put(DBProperties.SERVER_ROOT_URI, "bolt://localhost:7687"); - }) - .configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); - }); - } - - private MockUnit.Block serviceKey(final Env.ServiceKey serviceKey) { + .expect(hasPath(dbdir + ".url", false)) + .expect(hasPath(dbdir.toString(), false)) + .expect(hasPath(dbdir.toString(), false)) + .expect(hasPath("com.graphaware", false)) + .expect(hasPath("neo4j", false)) + .expect(hasPath("neo4j." + dbdir.toString(), false)) + .expect(props()) + .expect(setProp("database_dir", dbdir.toString())) + .expect(serviceKey()) + .expect(dbFactory(dbdir, dbdir.toString())) + .expect(embeddedAccess(dbdir.toString())) + .expect(logdb(null, dbdir.toString())) + .expect(onStop()) + .run(unit -> { + Neo4j neo4j = new Neo4j(dbdir); + neo4j.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); + }, closeOnStop(), + bind(GraphDatabaseService.class, 0), + bind(IDBAccess.class, 1), + unit -> { + IDBAccess db = unit.get(IDBAccess.class); + bind(db.getClass(), 2).run(unit); + }); + } + + @Test + public void oneModule() throws Exception { + Path dir = Paths.get("target", "des").toAbsolutePath(); + Path dbdir = dir.resolve("neo4jfs").toAbsolutePath(); + new MockUnit(Env.class, Config.class, Binder.class) + .expect(hasPath("db.url", false)) + .expect(hasPath("db", true)) + .expect(confString("db", dbdir.toString())) + .expect(hasPath("com.graphaware", true)) + .expect(confConf("com.graphaware", + ConfigFactory.empty().withValue("module", + ConfigValueFactory.fromAnyRef( + (ImmutableMap.of("class", ExpirationModuleBootstrapper.class.getName(), + "nodeExpirationProperty", "_expire")))))) + .expect(hasPath("neo4j", false)) + .expect(hasPath("db", false)) + .expect(hasPath("neo4j.db", false)) + .expect(props()) + .expect(setProp("database_dir", dbdir.toString())) + .expect(putProp("com.graphaware.module.m1.1", ExpirationModuleBootstrapper.class.getName())) + .expect(putProp("com.graphaware.module.m1.nodeExpirationProperty", "_expire")) + .expect(serviceKey()) + .expect(dbFactory(dbdir, "db")) + .expect(embeddedAccess("db")) + .expect(logdb(null, dbdir.toString())) + .expect(onStop()) + .run(unit -> { + Neo4j neo4j = new Neo4j(); + neo4j.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); + }, closeOnStop(), + bind(GraphDatabaseService.class, 0), + bind(IDBAccess.class, 1), + unit -> { + IDBAccess db = unit.get(IDBAccess.class); + bind(db.getClass(), 2).run(unit); + }); + } + + @Test + public void multipleModule() throws Exception { + Path dir = Paths.get("target", "des").toAbsolutePath(); + Path dbdir = dir.resolve("neo4jfs").toAbsolutePath(); + new MockUnit(Env.class, Config.class, Binder.class) + .expect(hasPath("db.url", false)) + .expect(hasPath("db", true)) + .expect(confString("db", dbdir.toString())) + .expect(hasPath("com.graphaware", true)) + .expect(confConf("com.graphaware", + ConfigFactory.empty().withValue("module", + ConfigValueFactory.fromAnyRef(Arrays.asList( + (ImmutableMap.of("class", ExpirationModuleBootstrapper.class.getName(), + "nodeExpirationProperty", "_expire")), + (ImmutableMap.of("class", "com.foo.Foo", + "foo", "bar"))))))) + .expect(hasPath("neo4j", false)) + .expect(hasPath("db", false)) + .expect(hasPath("neo4j.db", false)) + .expect(props()) + .expect(setProp("database_dir", dbdir.toString())) + .expect(putProp("com.graphaware.module.m1.1", ExpirationModuleBootstrapper.class.getName())) + .expect(putProp("com.graphaware.module.m1.nodeExpirationProperty", "_expire")) + .expect(putProp("com.graphaware.module.m2.2", "com.foo.Foo")) + .expect(putProp("com.graphaware.module.m2.foo", "bar")) + .expect(serviceKey()) + .expect(dbFactory(dbdir, "db")) + .expect(embeddedAccess("db")) + .expect(logdb(null, dbdir.toString())) + .expect(onStop()) + .run(unit -> { + Neo4j neo4j = new Neo4j(); + neo4j.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); + }, closeOnStop(), + bind(GraphDatabaseService.class, 0), + bind(IDBAccess.class, 1), + unit -> { + IDBAccess db = unit.get(IDBAccess.class); + bind(db.getClass(), 2).run(unit); + }); + } + + @SuppressWarnings({"unchecked", "deprecation" }) + @Test + public void embedded() throws Exception { + Path dir = Paths.get("target", "des").toAbsolutePath(); + Path dbdir = dir.resolve("neo4jfs").toAbsolutePath(); + new MockUnit(Env.class, Config.class, Binder.class) + .expect(hasPath("db.url", false)) + .expect(hasPath("db", true)) + .expect(confString("db", dbdir.toString())) + .expect(hasPath("com.graphaware", false)) + .expect(hasPath("neo4j", true)) + .expect(confConf("neo4j", + ConfigFactory.empty().withValue("unsupported.dbms.block_size.array_properties", + ConfigValueFactory.fromAnyRef(120)))) + .expect(hasPath("db", false)) + .expect(hasPath("neo4j.db", false)) + .expect(props()) + .expect(setProp("database_dir", dbdir.toString())) + .expect(putProp("unsupported.dbms.block_size.array_properties", 120)) + .expect(serviceKey()) + .expect(dbFactory(dbdir, "db")) + .expect(embeddedAccess("db")) + .expect(logdb(null, dbdir.toString())) + .expect(onStop()) + .expect(unit -> { + GraphDatabaseBuilder builder = unit.get(GraphDatabaseBuilder.class); + expect(builder.setConfig("unsupported.dbms.block_size.array_properties", "120")) + .andReturn(builder); + }) + .run(unit -> { + Neo4j neo4j = new Neo4j(); + neo4j.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class)); + }, closeOnStop(), + bind(GraphDatabaseService.class, 0), + bind(IDBAccess.class, 1), + unit -> { + IDBAccess db = unit.get(IDBAccess.class); + bind(db.getClass(), 2).run(unit); + }, unit -> { + unit.captured(BiConsumer.class).get(0) + .accept("unsupported.dbms.block_size.array_properties", "120"); + }); + } + + private Block closeOnStop() { + return unit -> { + unit.captured(CheckedRunnable.class).get(0).run(); + }; + } + + private Block onStop() { + return unit -> { + Env env = unit.get(Env.class); + unit.get(IDBAccess.class).close(); + expect(env.onStop(unit.capture(CheckedRunnable.class))).andReturn(env); + }; + } + + private Block logdb(final String uri, final String dir) { + return unit -> { + Properties props = unit.get(Properties.class); + expect(props.getProperty(DBProperties.SERVER_ROOT_URI)).andReturn(uri); + expect(props.getProperty(DBProperties.DATABASE_DIR)).andReturn(dir); + }; + } + + @SuppressWarnings({"unchecked", "rawtypes" }) + private Block embeddedAccess(final String dbkey) { + return unit -> { + EmbeddedDBAccess db = unit.constructor(EmbeddedDBAccess.class) + .build(); + unit.registerMock(IDBAccess.class, db); + db.initialize(unit.get(Properties.class)); + + LinkedBindingBuilder lbb = unit.mock(LinkedBindingBuilder.class); + lbb.toInstance(db); + lbb.toInstance(db); + + Binder binder = unit.get(Binder.class); + expect(binder.bind(Key.get(IDBAccess.class))).andReturn(lbb); + expect(binder.bind(Key.get(db.getClass()))).andReturn(lbb); + + ServiceKey keys = unit.get(ServiceKey.class); + keys.generate(eq(IDBAccess.class), eq(dbkey), unit.capture(Consumer.class)); + keys.generate(eq(db.getClass()), eq(dbkey), unit.capture(Consumer.class)); + }; + } + + @SuppressWarnings("unchecked") + private Block boltAccess(final String dbkey) { + return unit -> { + BoltDBAccess db = unit.constructor(BoltDBAccess.class) + .build(); + unit.registerMock(IDBAccess.class, db); + db.initialize(unit.get(Properties.class)); + + ServiceKey keys = unit.get(ServiceKey.class); + keys.generate(eq(IDBAccess.class), eq(dbkey), unit.capture(Consumer.class)); + keys.generate(eq(db.getClass()), eq(dbkey), unit.capture(Consumer.class)); + }; + } + + @SuppressWarnings("unchecked") + private Block dbFactory(final Path dbdir, final String dbkey) { + return unit -> { + GraphDatabaseService dbservice = unit.registerMock(GraphDatabaseService.class); + // unit.mockStatic(RuntimeRegistry.class); + // expect(RuntimeRegistry.getStartedRuntime(dbservice)).andReturn(null); + + LinkedBindingBuilder lbb = unit.mock(LinkedBindingBuilder.class); + lbb.toInstance(dbservice); + + Binder binder = unit.get(Binder.class); + expect(binder.bind(Key.get(GraphDatabaseService.class))).andReturn(lbb); + + ServiceKey keys = unit.get(ServiceKey.class); + keys.generate(eq(GraphDatabaseService.class), eq(dbkey), unit.capture(Consumer.class)); + + GraphDatabaseBuilder dbbuilder = unit.registerMock(GraphDatabaseBuilder.class); + expect(dbbuilder.newGraphDatabase()).andReturn(dbservice); + + GraphDatabaseFactory factory = unit.constructor(GraphDatabaseFactory.class) + .build(); + expect(factory.setUserLogProvider(isA(Slf4jLogProvider.class))).andReturn(factory); + expect(factory.newEmbeddedDatabaseBuilder(dbdir.toFile())).andReturn(dbbuilder); + }; + } + + private Block setProp(final String key, final String value) { return unit -> { + Properties props = unit.get(Properties.class); + expect(props.setProperty(key, value)).andReturn(null); + }; + } + + private Block putProp(final String key, final Object value) { + return unit -> { + Properties props = unit.get(Properties.class); + expect(props.put(key, value)).andReturn(null); + }; + } + + private Block serviceKey() { + return unit -> { + ServiceKey keys = unit.registerMock(Env.ServiceKey.class); Env env = unit.get(Env.class); - expect(env.serviceKey()).andReturn(serviceKey); + expect(env.serviceKey()).andReturn(keys); + }; + } + + @SuppressWarnings("unchecked") + private Block props() { + return unit -> { + Properties props = unit.constructor(Properties.class) + .build(); + props.forEach(unit.capture(BiConsumer.class)); + expectLastCall().times(0, 1); + unit.registerMock(Properties.class, props); + }; + } + + private Block notAConf(final String path) { + return unit -> { + Config conf = unit.get(Config.class); + expect(conf.getConfig(path)) + .andThrow(new ConfigException.WrongType(ConfigFactory.empty().origin(), path)); + }; + } + + private Block confString(final String path, final String value) { + return unit -> { + Config conf = unit.get(Config.class); + expect(conf.getString(path)).andReturn(value); + }; + } + + private Block hasPath(final String path, final boolean value) { + return unit -> { + Config conf = unit.get(Config.class); + expect(conf.hasPath(path)).andReturn(value); + }; + } + + private Block confConf(final String path, final Config value) { + return unit -> { + Config conf = unit.get(Config.class); + expect(conf.getConfig(path)).andReturn(value); + }; + } + + @SuppressWarnings({"unchecked", "rawtypes" }) + private Block bolt(final String uri, final String dbkey, final String user, final String pass) { + return unit -> { + AuthToken token = unit.mock(AuthToken.class); + + unit.mockStatic(AuthTokens.class); + expect(AuthTokens.basic(user, pass)).andReturn(token); + + Binder binder = unit.get(Binder.class); + + Session session = unit.registerMock(Session.class); + + LinkedBindingBuilder lbbs = unit.mock(LinkedBindingBuilder.class); + lbbs.toInstance(session); + expect(binder.bind(Key.get(Session.class))).andReturn(lbbs); + + Driver driver = unit.registerMock(Driver.class); + expect(driver.session()).andReturn(session); + + LinkedBindingBuilder lbb = unit.mock(LinkedBindingBuilder.class); + lbb.toInstance(driver); + + expect(binder.bind(Key.get(Driver.class))).andReturn(lbb); + + unit.mockStatic(GraphDatabase.class); + expect(GraphDatabase.driver(uri, token)).andReturn(driver); + + ServiceKey keys = unit.get(ServiceKey.class); + keys.generate(eq(Driver.class), eq(dbkey), unit.capture(Consumer.class)); + keys.generate(eq(Session.class), eq(dbkey), unit.capture(Consumer.class)); }; } -} \ No newline at end of file +} diff --git a/modules/jooby-neo4j/src/test/resources/embedded/application.conf b/modules/jooby-neo4j/src/test/resources/embedded/application.conf deleted file mode 100644 index a5008c711b..0000000000 --- a/modules/jooby-neo4j/src/test/resources/embedded/application.conf +++ /dev/null @@ -1,6 +0,0 @@ -################################################################################################### -# neo4j -################################################################################################### -neo4j.databaseDir = "C:/Users/i076326/Documents/programs/neo4j_db" -neo4j.session.label = "sample_app_session" -session.timeout = "1m" \ No newline at end of file diff --git a/modules/jooby-neo4j/src/test/resources/embedded/embedded_neo4j.conf b/modules/jooby-neo4j/src/test/resources/embedded/embedded_neo4j.conf deleted file mode 100644 index 753b9d1d28..0000000000 --- a/modules/jooby-neo4j/src/test/resources/embedded/embedded_neo4j.conf +++ /dev/null @@ -1,4 +0,0 @@ -################################################################################################### -# neo4j -################################################################################################### -neo4j.databaseDir = "/tmp" \ No newline at end of file diff --git a/modules/jooby-neo4j/src/test/resources/neo4j/neo4j.conf b/modules/jooby-neo4j/src/test/resources/neo4j/neo4j.conf deleted file mode 100644 index b256ec7509..0000000000 --- a/modules/jooby-neo4j/src/test/resources/neo4j/neo4j.conf +++ /dev/null @@ -1,9 +0,0 @@ -################################################################################################### -# neo4j -################################################################################################### -neo4j.server_root_uri = "bolt://localhost:7687" -neo4j.array_block_size = "120" -neo4j.pagecache_memory = "1M" -neo4j.string_block_size = "120" -neo4j.username = "****" -neo4j.password = "****" \ No newline at end of file diff --git a/pom.xml b/pom.xml index 12248a2aaa..5e3a7b9caa 100644 --- a/pom.xml +++ b/pom.xml @@ -292,6 +292,12 @@ ${jooby.version} + + org.jooby + jooby-neo4j + ${jooby.version} + + org.jooby jooby-assets @@ -1540,6 +1546,53 @@ ${consul.version} + + + com.graphaware.neo4j + runtime + ${neo4j.runtime.version} + + + + org.neo4j + neo4j-graphdb-api + ${neo4j.version} + + + + org.neo4j + neo4j + ${neo4j.version} + + + + org.neo4j + neo4j-slf4j + ${neo4j.version} + + + + com.graphaware.neo4j + expire + ${neo4j.expire.version} + + + + net.iot-solutions.graphdb + jcypher + ${jcypher.version} + + + org.neo4j + neo4j-kernel + + + org.neo4j + neo4j-io + + + + junit @@ -2957,6 +3010,10 @@ org.eclipse.jdt.apt.processorOptions/defaultOverwrite=true 0.16.0 1.1.2-5 1.2.0 + 3.2.1.51 + 3.2.1.51.4 + 3.2.1 + 3.7.0 **