Skip to content
This repository has been archived by the owner on Mar 12, 2020. It is now read-only.

Commit

Permalink
Name all properties and use one session per transaction #19
Browse files Browse the repository at this point in the history
Sessions are not thread-safe in Neo4J.
Therefore, we have to create a new session per transaction.
Simplify the transactions and name all the properties.
  • Loading branch information
tdauth committed Apr 10, 2019
1 parent 977fd68 commit 7a6f13e
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,26 @@ class ActionTransitionEntity(s: SutStateEntity, e: SutStateEntity, a: String) {
@GeneratedValue
var id: java.lang.Long = null

@Property(name = ActionTransitionEntity.PropertyNameStart)
@Index
@StartNode var start: SutStateEntity = s

@Property(name = ActionTransitionEntity.PropertyNameEnd)
@Index
@EndNode var end: SutStateEntity = e

@Property(name = ActionTransitionEntity.PropertyNameAction)
@Index
var action: String = a

/// The number of times this action has been executed.
@Property(name = ActionTransitionEntity.PropertyNameCounter)
var counter: Int = 1
}

object ActionTransitionEntity {
final val PropertyNameStart = "start"
final val PropertyNameEnd = "end"
final val PropertyNameAction = "action"
final val PropertyNameCounter = "counter"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ package de.retest.guistatemachine.api.neo4j
import com.typesafe.scalalogging.Logger
import de.retest.guistatemachine.api.{GuiStateMachine, State, SutStateIdentifier}
import org.neo4j.ogm.cypher.{ComparisonOperator, Filter}
import org.neo4j.ogm.session.Session

import scala.collection.immutable.HashMap

class GuiStateMachineNeo4J(var uri: String) extends GuiStateMachine {
private val logger = Logger[GuiStateMachineNeo4J]
implicit val session = Neo4jSessionFactory.getSessionFactory(uri).openSession() // TODO #19 Close the session at some point?

override def getState(sutStateIdentifier: SutStateIdentifier): State = {
val result = Neo4jSessionFactory.transaction {
getNodeBySutStateIdentifier(sutStateIdentifier) match {
val result = Neo4jSessionFactory.transaction { session =>
getNodeBySutStateIdentifier(session, sutStateIdentifier) match {
case None =>
// Create a new node for the SUT state in the graph database.
val sutStateEntity = new SutStateEntity(sutStateIdentifier)
Expand All @@ -22,7 +22,7 @@ class GuiStateMachineNeo4J(var uri: String) extends GuiStateMachine {
// Do nothing if the node for the SUT state does already exist.
case Some(_) => false
}
}
}(uri)

if (result) {
logger.info(s"Created new state from SUT state identifier $sutStateIdentifier.")
Expand All @@ -32,7 +32,7 @@ class GuiStateMachineNeo4J(var uri: String) extends GuiStateMachine {
}

override def getAllStates: Map[SutStateIdentifier, State] =
Neo4jSessionFactory.transaction {
Neo4jSessionFactory.transaction { session =>
val allNodes = session.loadAll(classOf[SutStateEntity])
var result = HashMap[SutStateIdentifier, State]()
val iterator = allNodes.iterator()
Expand All @@ -43,23 +43,32 @@ class GuiStateMachineNeo4J(var uri: String) extends GuiStateMachine {
result = result + (sutState -> StateNeo4J(sutState, this))
}
result
}
}(uri)

override def clear(): Unit = session.purgeDatabase()
override def clear(): Unit =
Neo4jSessionFactory.transaction { session =>
session.purgeDatabase()
}(uri)

override def assignFrom(other: GuiStateMachine): Unit = {
// TODO #19 Should we delete the old graph database?
val otherStateMachine = other.asInstanceOf[GuiStateMachineNeo4J]
uri = otherStateMachine.uri
}

private[neo4j] def getNodeBySutStateIdentifier(sutStateIdentifier: SutStateIdentifier): Option[SutStateEntity] = {
val filter = new Filter("hash", ComparisonOperator.EQUALS, sutStateIdentifier.hash)
private[neo4j] def getNodeBySutStateIdentifier(session: Session, sutStateIdentifier: SutStateIdentifier): Option[SutStateEntity] = {
val filter = new Filter(SutStateEntity.PropertyNameHash, ComparisonOperator.EQUALS, sutStateIdentifier.hash)
val first = session.loadAll(classOf[SutStateEntity], filter).stream().findFirst()
if (first.isPresent) {
Some(first.get())
} else {
None
}
}

private[neo4j] def getNodeBySutStateIdentifierOrThrow(session: Session, sutStateIdentifier: SutStateIdentifier): SutStateEntity =
getNodeBySutStateIdentifier(session, sutStateIdentifier) match {
case Some(sutStateEntity) => sutStateEntity
case None => throw new RuntimeException(s"Missing state $sutStateIdentifier")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ object Neo4jSessionFactory {
try {
val transaction = session.beginTransaction()
txn = Some(transaction)
val r = f
val r = f(session)
transaction.commit()
r
} finally {
Expand Down
103 changes: 42 additions & 61 deletions src/main/scala/de/retest/guistatemachine/api/neo4j/StateNeo4J.scala
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
package de.retest.guistatemachine.api.neo4j
import de.retest.guistatemachine.api.{ActionIdentifier, ActionTransitions, State, SutStateIdentifier}
import org.neo4j.ogm.cypher.{ComparisonOperator, Filter}

import scala.collection.JavaConversions._
import scala.collection.immutable.HashMap

case class StateNeo4J(sutStateIdentifier: SutStateIdentifier, guiStateMachine: GuiStateMachineNeo4J) extends State {
implicit val session = guiStateMachine.session

override def getSutStateIdentifier: SutStateIdentifier = sutStateIdentifier
// TODO #19 Can we somehow convert the outgoing relations directly from the SutStateEntity?
override def getOutgoingActionTransitions: Map[ActionIdentifier, ActionTransitions] =
Neo4jSessionFactory.transaction {
val filter = new Filter("start", ComparisonOperator.EQUALS, sutStateIdentifier.hash)
val transitions = session.loadAll(classOf[ActionTransitionEntity], filter)
Neo4jSessionFactory.transaction { session =>
val sutStateEntity = guiStateMachine.getNodeBySutStateIdentifierOrThrow(session, sutStateIdentifier)
var result = HashMap[ActionIdentifier, ActionTransitions]()
val iterator = transitions.iterator()
val iterator = sutStateEntity.outgoingActionTransitions.iterator()
while (iterator.hasNext) {
val relationship = iterator.next()
val action = new ActionIdentifier(relationship.action)
Expand All @@ -29,62 +26,46 @@ case class StateNeo4J(sutStateIdentifier: SutStateIdentifier, guiStateMachine: G
result = result + (action -> actionTransitions)
}
result
}
// TODO #19 Can we somehow convert the incoming relations directly from the SutStateEntity?
def getIncomingActionTransitions: Map[ActionIdentifier, ActionTransitions] = Neo4jSessionFactory.transaction {
val filter = new Filter("end", ComparisonOperator.EQUALS, sutStateIdentifier.hash)
val transitions = session.loadAll(classOf[ActionTransitionEntity], filter)
var result = HashMap[ActionIdentifier, ActionTransitions]()
val iterator = transitions.iterator()
while (iterator.hasNext) {
val relationship = iterator.next()
val action = new ActionIdentifier(relationship.action)
val sourceSutState = new SutStateIdentifier(relationship.start.hash)
val counter = relationship.counter
val actionTransitions = if (result.contains(action)) {
val existing = result(action)
ActionTransitions(existing.states ++ Set(StateNeo4J(sourceSutState, guiStateMachine)), existing.executionCounter + counter)
} else {
ActionTransitions(Set(StateNeo4J(sourceSutState, guiStateMachine)), counter)
}
result = result + (action -> actionTransitions)
}
result
}
}(guiStateMachine.uri)

// TODO #19 Can we somehow add the transition to the incoming and outgoing relations?
private[api] override def addTransition(a: ActionIdentifier, to: State): Int = Neo4jSessionFactory.transaction {
/*
TODO #19 Filter for start and end states.
val filterStart = new Filter("start", ComparisonOperator.EQUALS, sutStateIdentifier.hash)
val filterEnd = new Filter("end", ComparisonOperator.EQUALS, targetSutStateIdentifier.hash)
filterStart.and(filterAction).and(filterEnd)
*/
val filterAction = new Filter("action", ComparisonOperator.EQUALS, a.hash)
val targetSutStateIdentifier = to.asInstanceOf[StateNeo4J].sutStateIdentifier
def getIncomingActionTransitions: Map[ActionIdentifier, ActionTransitions] =
Neo4jSessionFactory.transaction { session =>
val sutStateEntity = guiStateMachine.getNodeBySutStateIdentifierOrThrow(session, sutStateIdentifier)
var result = HashMap[ActionIdentifier, ActionTransitions]()
val iterator = sutStateEntity.incomingActionTransitions.iterator()
while (iterator.hasNext) {
val relationship = iterator.next()
val action = new ActionIdentifier(relationship.action)
val sourceSutState = new SutStateIdentifier(relationship.start.hash)
val counter = relationship.counter
val actionTransitions = if (result.contains(action)) {
val existing = result(action)
ActionTransitions(existing.states ++ Set(StateNeo4J(sourceSutState, guiStateMachine)), existing.executionCounter + counter)
} else {
ActionTransitions(Set(StateNeo4J(sourceSutState, guiStateMachine)), counter)
}
result = result + (action -> actionTransitions)
}
result
}(guiStateMachine.uri)

import scala.collection.JavaConversions._
val transitions = session.loadAll(classOf[ActionTransitionEntity], filterAction).toSeq
private[api] override def addTransition(a: ActionIdentifier, to: State): Int =
Neo4jSessionFactory.transaction { session =>
val sourceState = guiStateMachine.getNodeBySutStateIdentifierOrThrow(session, sutStateIdentifier)
val targetSutStateIdentifier = to.asInstanceOf[StateNeo4J].sutStateIdentifier
val targetState = guiStateMachine.getNodeBySutStateIdentifierOrThrow(session, targetSutStateIdentifier)
val matchingTransitions =
sourceState.outgoingActionTransitions.toSeq.filter(actionTransition => actionTransition.end == targetState && actionTransition.action == a.hash)

val matchingTransitions = transitions.filter(actionTransitionEntity =>
actionTransitionEntity.start.hash == sutStateIdentifier.hash && actionTransitionEntity.end.hash == targetSutStateIdentifier.hash)
if (matchingTransitions.nonEmpty) {
val first: ActionTransitionEntity = matchingTransitions.head
first.counter = first.counter + 1
session.save(first)
first.counter
} else {
guiStateMachine.getNodeBySutStateIdentifier(sutStateIdentifier) match {
case Some(sourceState) =>
guiStateMachine.getNodeBySutStateIdentifier(targetSutStateIdentifier) match {
case Some(targetState) =>
val transition = new ActionTransitionEntity(sourceState, targetState, a.hash)
session.save(transition)
1
case None => throw new RuntimeException(s"Missing target state $targetSutStateIdentifier.")
}
case None => throw new RuntimeException(s"Missing source state $sutStateIdentifier.")
if (matchingTransitions.nonEmpty) {
val first = matchingTransitions.head
first.counter = first.counter + 1
session.save(first)
first.counter
} else {
val transition = new ActionTransitionEntity(sourceState, targetState, a.hash)
session.save(transition)
1
}
}
}
}(guiStateMachine.uri)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import org.neo4j.ogm.annotation._

@NodeEntity
class SutStateEntity(
@Property(name = SutStateEntity.PropertyNameHash)
@Index(unique = true)
@Property
var hash: java.lang.String) {

def this(sutStateIdentifier: SutStateIdentifier) = this(sutStateIdentifier.hash)
Expand All @@ -16,11 +16,10 @@ class SutStateEntity(
@GeneratedValue
var id: java.lang.Long = null

@Relationship(`type` = "ACTIONS", direction = Relationship.UNDIRECTED) var actionTransitions = new java.util.LinkedList[ActionTransitionEntity]()
@Relationship(`type` = "ACTIONS", direction = Relationship.OUTGOING) var incomingActionTransitions = new java.util.LinkedList[ActionTransitionEntity]()
@Relationship(`type` = "ACTIONS", direction = Relationship.INCOMING) var outgoingActionTransitions = new java.util.LinkedList[ActionTransitionEntity]()
}

// TODO #19 Which container types?
/*
@Relationship(`type` = "ACTIONS", direction = Relationship.INCOMING) var incomingActionTransitions = new java.util.LinkedList[ActionTransitionEntity]()
@Relationship(`type` = "ACTIONS", direction = Relationship.OUTGOING) var outgoingActionTransitions = new java.util.LinkedList[ActionTransitionEntity]()
*/
object SutStateEntity {
final val PropertyNameHash = "hash"
}

0 comments on commit 7a6f13e

Please sign in to comment.