From 7a6f13e568d69fb20ea11d6f9438e29000337106 Mon Sep 17 00:00:00 2001 From: Tamino Dauth Date: Wed, 10 Apr 2019 15:04:07 +0200 Subject: [PATCH] Name all properties and use one session per transaction #19 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. --- .../api/neo4j/ActionTransitionEntity.scala | 11 ++ .../api/neo4j/GuiStateMachineNeo4J.scala | 27 +++-- .../api/neo4j/Neo4jSessionFactory.scala | 2 +- .../api/neo4j/StateNeo4J.scala | 103 +++++++----------- .../api/neo4j/SutStateEntity.scala | 13 +-- 5 files changed, 78 insertions(+), 78 deletions(-) diff --git a/src/main/scala/de/retest/guistatemachine/api/neo4j/ActionTransitionEntity.scala b/src/main/scala/de/retest/guistatemachine/api/neo4j/ActionTransitionEntity.scala index c3e3012..29ea4ae 100644 --- a/src/main/scala/de/retest/guistatemachine/api/neo4j/ActionTransitionEntity.scala +++ b/src/main/scala/de/retest/guistatemachine/api/neo4j/ActionTransitionEntity.scala @@ -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" +} diff --git a/src/main/scala/de/retest/guistatemachine/api/neo4j/GuiStateMachineNeo4J.scala b/src/main/scala/de/retest/guistatemachine/api/neo4j/GuiStateMachineNeo4J.scala index e6a0ae1..0eda518 100644 --- a/src/main/scala/de/retest/guistatemachine/api/neo4j/GuiStateMachineNeo4J.scala +++ b/src/main/scala/de/retest/guistatemachine/api/neo4j/GuiStateMachineNeo4J.scala @@ -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) @@ -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.") @@ -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() @@ -43,9 +43,12 @@ 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? @@ -53,8 +56,8 @@ class GuiStateMachineNeo4J(var uri: String) extends GuiStateMachine { 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()) @@ -62,4 +65,10 @@ class GuiStateMachineNeo4J(var uri: String) extends GuiStateMachine { 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") + } } diff --git a/src/main/scala/de/retest/guistatemachine/api/neo4j/Neo4jSessionFactory.scala b/src/main/scala/de/retest/guistatemachine/api/neo4j/Neo4jSessionFactory.scala index 8501446..b7a605c 100644 --- a/src/main/scala/de/retest/guistatemachine/api/neo4j/Neo4jSessionFactory.scala +++ b/src/main/scala/de/retest/guistatemachine/api/neo4j/Neo4jSessionFactory.scala @@ -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 { diff --git a/src/main/scala/de/retest/guistatemachine/api/neo4j/StateNeo4J.scala b/src/main/scala/de/retest/guistatemachine/api/neo4j/StateNeo4J.scala index 7bd103d..c913ed9 100644 --- a/src/main/scala/de/retest/guistatemachine/api/neo4j/StateNeo4J.scala +++ b/src/main/scala/de/retest/guistatemachine/api/neo4j/StateNeo4J.scala @@ -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) @@ -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) } diff --git a/src/main/scala/de/retest/guistatemachine/api/neo4j/SutStateEntity.scala b/src/main/scala/de/retest/guistatemachine/api/neo4j/SutStateEntity.scala index 48c5956..ed3c082 100644 --- a/src/main/scala/de/retest/guistatemachine/api/neo4j/SutStateEntity.scala +++ b/src/main/scala/de/retest/guistatemachine/api/neo4j/SutStateEntity.scala @@ -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) @@ -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" }