diff --git a/modules/coverage-report/pom.xml b/modules/coverage-report/pom.xml index 32c1ba31e1..4598528340 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 diff --git a/modules/jooby-neo4j/foo.conf b/modules/jooby-neo4j/foo.conf new file mode 100644 index 0000000000..5f56c8b494 --- /dev/null +++ b/modules/jooby-neo4j/foo.conf @@ -0,0 +1 @@ +com.graphaware.runtime.enabled = false \ No newline at end of file diff --git a/modules/jooby-neo4j/pom.xml b/modules/jooby-neo4j/pom.xml index 4d66e0445d..b6d7377521 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 @@ -44,25 +44,55 @@ expire 3.1.4.49.4 + com.graphaware.neo4j runtime 3.1.4.49 + org.neo4j neo4j-graphdb-api 3.1.4 + org.neo4j neo4j 3.1.4 + + + org.neo4j + neo4j-slf4j + 3.1.4 + + + + org.slf4j + jul-to-slf4j + + + + org.javassist + javassist + + net.iot-solutions.graphdb jcypher 3.7.0 + + + org.neo4j + neo4j-kernel + + + org.neo4j + neo4j-io + + 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..b2681387ee 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,245 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - package org.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.graphaware.runtime.RuntimeRegistry; 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; +public class Neo4j implements Module { -import static com.google.common.base.Preconditions.checkArgument; -import static java.util.Objects.nonNull; -import static java.util.Objects.requireNonNull; + static { + if (!SLF4JBridgeHandler.isInstalled()) { + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + } + } + + /** The logging system. */ + private final Logger log = LoggerFactory.getLogger(getClass()); + + private String db; -/** - * Exposes {@link BoltDBAccess}. - * - *

usage

- * - *

- * application.conf: - *

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

- * application.conf: - *

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

options

- *

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

- * - *
- * neo4j.server_root_uri  = "bolt://localhost:7687"
- * 
- * - *

- * or programmatically: - *

- * - *
- * {
- *   use(new Neo4j()
- *     .properties((properties, config) {@literal ->} {
- *       properties.put(DBProperties.SERVER_ROOT_URI, config.get("server_root_uri"))
- *     })
- *   );
- * }
- * 
- * - *

- * Default connection URI is defined by the server_root_uri property. Neo4j URI looks like: - *

- * - *
- *   bolt://host1[:port1]
- * 
- * - *

- * Credentials have to be passed separately in username & password params. - *

- * - *

two or more connections

- * - *
- * db1 = "bolt://localhost:7687"
- * db2 = "bolt://localhost:7688"
- * 
- * - *
- * {
- *   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
- *   });
- * }
- * 
- * - * @author sbcd90 - * @since 1.2.0 - */ -public class Neo4j implements Jooby.Module { - - private final String db; - - private BiConsumer properties; - - /** - * Creates a new {@link Neo4j} module. - * - * @param db Name of the property with the connection info. - */ public Neo4j(final String db) { - this.db = requireNonNull(db, "A neo4j bolt configuration is required"); + this.db = requireNonNull(db, "Database required."); } - /** - * Creates a new {@link Neo4j} using the default property: db. - */ 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); - String dbUser = nonNull(properties.getProperty("username")) ? properties.getProperty("username"): ""; - String dbPassword = nonNull(properties.getProperty("password")) ? properties.getProperty("password"): ""; + Arrays.asList(props.getProperty(SERVER_ROOT_URI), props.getProperty(DATABASE_DIR)) + .stream() + .filter(Objects::nonNull) + .findFirst() + .ifPresent(it -> log.info("Starting neo4j: {}", it)); - BoltDBAccess remoteClient; - if (dbUser.isEmpty() && dbPassword.isEmpty()) { - remoteClient = (BoltDBAccess) DBAccessFactory.createDBAccess(DBType.REMOTE, properties); - } else { - remoteClient = (BoltDBAccess) DBAccessFactory.createDBAccess(DBType.REMOTE, properties, dbUser, dbPassword); - } - checkArgument(remoteClient.getSession() != null, - "Cannot connect to Database at: " + properties.get(DBProperties.SERVER_ROOT_URI)); - String database = properties.getProperty(DBProperties.SERVER_ROOT_URI); + 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.ServiceKey serviceKey = env.serviceKey(); - serviceKey.generate(BoltDBAccess.class, database, k -> binder.bind(k).toInstance(remoteClient)); + env.onStop(dbaccess::close); + } - env.onStop(remoteClient::close); + @Override + public Config config() { + return ConfigFactory.empty(getClass().getName().toLowerCase() + ".conf") + .withValue("neo4j.session.label", ConfigValueFactory.fromAnyRef("session")); } - /** - * 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 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; } - @Override - public Config config() { - return ConfigFactory.parseResources(Neo4j.class, "neo4j.conf"); + @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); + } + GraphDatabaseBuilder builder = new GraphDatabaseFactory() + .setUserLogProvider(new Slf4jLogProvider()) + .newEmbeddedDatabaseBuilder(path.toFile()); + props.forEach((k, v) -> builder.setConfig(k.toString(), v.toString())); + GraphDatabaseService graphDatabaseService = builder.newGraphDatabase(); + RuntimeRegistry.getStartedRuntime(graphDatabaseService); + keys.generate(GraphDatabaseService.class, dbkey, + k -> binder.bind(k).toInstance(graphDatabaseService)); + + EmbeddedDBAccess dbaccess = new EmbeddedDBAccess(); + dbaccess.initialize(props); + dbServicehack(dbaccess, graphDatabaseService); + + return dbaccess; } - 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))); + 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; + } + + 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; + } + + static void driverhack(final BoltDBAccess dbaccess, final Driver value) throws Exception { + fieldhack(BoltDBAccess.class, dbaccess, "driver", value); + } - return properties; + static void dbServicehack(final EmbeddedDBAccess dbaccess, final GraphDatabaseService value) + throws Exception { + fieldhack(AbstractEmbeddedDBAccess.class, dbaccess, "graphDb", value); } - private Config neo4j(final Config config) { - Config $neo4j = config.getConfig("neo4j"); + @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); + } - if (config.hasPath("neo4j." + db)) { - $neo4j = config.getConfig("neo4j." + db).withFallback($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; + } + + }); } - 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..940c0f1c11 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 @@ -102,110 +112,132 @@ */ public class Neo4jSessionStore implements Session.Store { - private static final char DOT = '.'; + private final Set SPECIAL = ImmutableSet.of("_accessedAt", "_createdAt", "_savedAt", + "_expire", "_id"); - private static final char UDOT = '\uFF0E'; + /** The logging system. */ + private final Logger log = LoggerFactory.getLogger(getClass()); - private static final char DOLLAR = '$'; + private final String label; - private static final char UDOLLAR = '\uFF04'; + private final LongSupplier expire; - protected final String sessionLabel; + private final IDBAccess db; - protected final long timeout; - - private static GraphDatabaseService dbService; - private static GraphAwareRuntime graphRuntime; - - protected final GraphDatabaseService db; - protected final GraphAwareRuntime graphAwareRuntime; - - - 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; + 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); + attributes.put("_id", sid); + attributes.forEach((k, v) -> { + create.property(k).value(v); }); - tx.success(); + + 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 +245,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..039c06b6f8 --- /dev/null +++ b/modules/jooby-neo4j/src/test/java/org/jooby/neo4j/Neo4jSessionStoreTest.java @@ -0,0 +1,287 @@ +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.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 }) +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)); + }); + } + + @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)); + } + }; + } + + 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..d4111be4bc 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,529 @@ 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)); + } + + @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); + }); + } - 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); + @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)); + }; + } - Env env = unit.get(Env.class); - expect(env.onStop(unit.capture(Try.CheckedRunnable.class))).andReturn(env); - }; + @Test + 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(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 defaults() throws Exception { + 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(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", 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 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 -> { - 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", 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 defaultsWithCustomAction() throws Exception { + 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(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("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 defaultsConfig() throws Exception { + 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(unit -> { - assertEquals($neo4j, new Neo4j().config()); - }); + .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 defaultsWithProperties() throws Exception { + 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(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("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 +}