This repository has been archived by the owner on Mar 12, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split Bolt and Embedded implementation for Neo4J #19
Neo4JConfig allows different implementations. GuiStateMachineApi must be implemented for embedded databases/Bolt. We cache the session factories which is required for embedded databases.
- Loading branch information
Showing
12 changed files
with
210 additions
and
125 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 0 additions & 51 deletions
51
src/main/scala/de/retest/guistatemachine/api/neo4j/GuiStateMachineApiNeo4J.scala
This file was deleted.
Oops, something went wrong.
19 changes: 19 additions & 0 deletions
19
src/main/scala/de/retest/guistatemachine/api/neo4j/GuiStateMachineApiNeo4JBolt.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package de.retest.guistatemachine.api.neo4j | ||
|
||
import com.typesafe.scalalogging.Logger | ||
import de.retest.guistatemachine.api.{GuiStateMachine, GuiStateMachineApi} | ||
|
||
class GuiStateMachineApiNeo4JBolt(url: String, port: Int, user: String, password: String) extends GuiStateMachineApi { | ||
private val logger = Logger[GuiStateMachineApiNeo4JBolt] | ||
|
||
override def createStateMachine(name: String): GuiStateMachine = { | ||
val conf = BoltConfig(url, port, user, password) // TODO #19 How to distinguish between databases. | ||
new GuiStateMachineNeo4J(conf, Neo4JSessionFactory.getSessionFactory(conf)) | ||
} | ||
|
||
override def removeStateMachine(name: String): Boolean = false // TODO #19 How to remove a Bolt database? | ||
|
||
override def getStateMachine(name: String): Option[GuiStateMachine] = None // TODO #19 How to get a Bolt database? Some list? | ||
|
||
override def clear(): Unit = {} // TODO #19 How to remove all Bolt databases? | ||
} |
75 changes: 75 additions & 0 deletions
75
src/main/scala/de/retest/guistatemachine/api/neo4j/GuiStateMachineApiNeo4JEmbedded.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package de.retest.guistatemachine.api.neo4j | ||
|
||
import java.io.File | ||
import java.nio.file.{Path, Paths} | ||
|
||
import com.typesafe.scalalogging.Logger | ||
import de.retest.guistatemachine.api.{GuiStateMachine, GuiStateMachineApi} | ||
import org.apache.commons.io.FileUtils | ||
|
||
/** | ||
* This implementation is only thread-safe but cannot be used by multiple processes on the same storage directory. | ||
* There should be only one single instance of this implementation per storage directory shared in the whole application. | ||
* @param storageDirectory The directory where all subdirectories for embedded graph databases are created. | ||
*/ | ||
class GuiStateMachineApiNeo4JEmbedded(storageDirectory: Path) extends GuiStateMachineApi { | ||
private val logger = Logger[GuiStateMachineApiNeo4JEmbedded] | ||
|
||
override def createStateMachine(name: String): GuiStateMachine = synchronized { | ||
if (isDirectory(name)) { | ||
throw new RuntimeException(s"State machine $name does already exist.") | ||
} else { | ||
val path = getPath(name) | ||
logger.info("Created new graph DB in {}.", path) | ||
val conf = EmbeddedConfig(path) | ||
val sessionFactory = Neo4JSessionFactory.getSessionFactory(conf) | ||
new GuiStateMachineNeo4J(conf, sessionFactory) | ||
} | ||
} | ||
|
||
override def removeStateMachine(name: String): Boolean = synchronized { | ||
if (isDirectory(name)) { | ||
val file = getFile(name) | ||
logger.info("Deleting state machine in {}.", file) | ||
FileUtils.deleteDirectory(file) | ||
true | ||
} else { | ||
false | ||
} | ||
} | ||
|
||
override def getStateMachine(name: String): Option[GuiStateMachine] = synchronized { | ||
if (isDirectory(name)) { | ||
val path = getPath(name) | ||
logger.info("Getting graph DB in {}.", path) | ||
val conf = EmbeddedConfig(path) | ||
val sessionFactory = Neo4JSessionFactory.getSessionFactory(conf) | ||
Some(new GuiStateMachineNeo4J(conf, sessionFactory)) | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
override def clear(): Unit = synchronized { | ||
val storageDir = storageDirectory.toFile | ||
if (storageDir.isDirectory) { | ||
storageDir.listFiles().toSeq foreach { file => | ||
Neo4JSessionFactory.removeSessionFactory(EmbeddedConfig(file.toPath)) | ||
} | ||
logger.info("Deleting all state machines in {}.", storageDirectory) | ||
FileUtils.deleteDirectory(storageDir) | ||
} else { | ||
logger.info("Directory {} does not exist.", storageDirectory) | ||
} | ||
} | ||
|
||
/** | ||
* Gets the path of the embedded database with a certain name relative to the storage directory path. | ||
* It needs to add an "embedded" directory since the parent directory will contain the lock and a logs directory. | ||
* @param name The name of the embedded database. | ||
* @return The path of the directory of the embedded database. | ||
*/ | ||
private def getPath(name: String): Path = storageDirectory.resolve(Paths.get(name, "embedded")) | ||
private def getFile(name: String): File = getPath(name).toFile | ||
private def isDirectory(name: String): Boolean = getFile(name).isDirectory | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
21 changes: 21 additions & 0 deletions
21
src/main/scala/de/retest/guistatemachine/api/neo4j/Neo4JConfig.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package de.retest.guistatemachine.api.neo4j | ||
|
||
import java.nio.file.Path | ||
|
||
import org.neo4j.ogm.config.Configuration | ||
|
||
sealed trait Neo4JConfig { | ||
def buildConfig(): Configuration | ||
} | ||
|
||
case class EmbeddedConfig(path: Path) extends Neo4JConfig { | ||
override def buildConfig(): Configuration = new Configuration.Builder().uri(path.toUri.toString).build | ||
} | ||
|
||
case class BoltConfig(host: String, port: Int, user: String, password: String) extends Neo4JConfig { | ||
override def buildConfig(): Configuration = | ||
new Configuration.Builder() | ||
.uri(s"bolt://$host:$port") | ||
.credentials(user, password) | ||
.build() | ||
} |
46 changes: 46 additions & 0 deletions
46
src/main/scala/de/retest/guistatemachine/api/neo4j/Neo4JSessionFactory.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package de.retest.guistatemachine.api.neo4j | ||
|
||
import com.typesafe.scalalogging.Logger | ||
import org.neo4j.ogm.session.SessionFactory | ||
|
||
import scala.collection.concurrent.TrieMap | ||
|
||
sealed trait Neo4JSessionFactory | ||
|
||
object Neo4JSessionFactory { | ||
private val logger = Logger[Neo4JSessionFactory] | ||
|
||
/** | ||
* Session factories should always be shared in the application. Besides, we have to avoid exceptions like: | ||
* ``` | ||
* org.neo4j.kernel.StoreLockException: Unable to obtain lock on store lock file: /tmp/GuiStateMachineApiNeo4jSpec8181209634775261316/store_lock. | ||
* Please ensure no other process is using this database, and that the directory is writable (required even for read-only access) | ||
* ``` | ||
* Apparently, Neo4J does not support multiple processes to access the same embedded database. | ||
* TODO #19 Can we allow access by multiple processes on the same database. | ||
* TODO #19 When do we close this? | ||
*/ | ||
private val sessionFactories = TrieMap[Neo4JConfig, SessionFactory]() | ||
|
||
def getSessionFactory(neo4JConfig: Neo4JConfig): SessionFactory = sessionFactories.get(neo4JConfig) match { | ||
case Some(sessionFactory) => | ||
logger.info("Reusing session factory for {}", neo4JConfig) | ||
sessionFactory | ||
case None => | ||
logger.info("Creating new session factory for {}", neo4JConfig) | ||
val conf = neo4JConfig.buildConfig() | ||
val packageName = this.getClass.getPackage.getName | ||
val sessionFactory = new SessionFactory(conf, packageName) | ||
sessionFactories += (neo4JConfig -> sessionFactory) | ||
sessionFactory | ||
|
||
} | ||
|
||
def removeSessionFactory(neo4JConfig: Neo4JConfig): Boolean = sessionFactories.remove(neo4JConfig) match { | ||
case Some(sessionFactory) => | ||
logger.info("Closing session factory for {}", neo4JConfig) | ||
sessionFactory.close() | ||
true | ||
case None => false | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
src/main/scala/de/retest/guistatemachine/api/neo4j/Neo4JUtil.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package de.retest.guistatemachine.api.neo4j | ||
|
||
import org.neo4j.ogm.session.{Session, SessionFactory} | ||
import org.neo4j.ogm.transaction.Transaction | ||
|
||
object Neo4JUtil { | ||
|
||
def transaction[A](f: Session => A)(implicit sessionFactory: SessionFactory): A = { | ||
// We have to create a session for every transaction since sessions are not thread-safe. | ||
val session = sessionFactory.openSession() | ||
var txn: Option[Transaction] = None | ||
try { | ||
val transaction = session.beginTransaction() | ||
txn = Some(transaction) | ||
val r = f(session) | ||
transaction.commit() | ||
r | ||
} finally { | ||
txn match { | ||
case Some(transaction) => transaction.close() | ||
case None => | ||
} | ||
} | ||
} | ||
} |
52 changes: 0 additions & 52 deletions
52
src/main/scala/de/retest/guistatemachine/api/neo4j/Neo4jSessionFactory.scala
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.