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

Commit

Permalink
Store actions as XML #19
Browse files Browse the repository at this point in the history
  • Loading branch information
tdauth committed Apr 9, 2019
1 parent 2519287 commit 890fc0d
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 13 deletions.
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ libraryDependencies += "javax.xml.bind" % "jaxb-api" % "2.3.0"
libraryDependencies += "org.neo4j" % "neo4j" % "3.0.1"
libraryDependencies += "org.neo4j" % "neo4j-ogm-core" % "3.1.7"
libraryDependencies += "org.neo4j" % "neo4j-ogm-embedded-driver" % "3.1.7"
libraryDependencies += "org.scala-lang.modules" %% "scala-xml" % "1.1.1"

// Dependencies to write GML files for yEd:
libraryDependencies += "com.github.systemdir.gml" % "GMLWriterForYed" % "2.1.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,113 @@
package de.retest.guistatemachine.api.neo4j

import de.retest.surili.commons.actions.{Action, NavigateRefreshAction}
import de.retest.recheck.ui.descriptors.{Element, SutState}
import de.retest.surili.commons.actions._
import org.neo4j.ogm.typeconversion.AttributeConverter

class ActionConverter extends AttributeConverter[Action, String] {
def toGraphProperty(value: Action): String = value.toString // TODO #19 convert to XML with element
def toEntityAttribute(value: String): Action = new NavigateRefreshAction // TODO #19 convert from XML to an action
import scala.xml._

/**
* We do not want to store the whole target element as XML again. Hence, we store its retest ID which is unique and
* matches the element in the SUT state and only the additional attributes.
*/
class ActionConverter(val sutState: Option[SutState]) extends AttributeConverter[Action, String] {

def this() = this(None)

def toGraphProperty(value: Action): String = {
val nodeBuffer = new NodeBuffer

nodeBuffer += <type>{value.getClass.getSimpleName}</type>

if (value.getTargetElement.isPresent) {
val retestId = value.getTargetElement.get().getRetestId
nodeBuffer += <retestId>{retestId}</retestId>
}

value match {
case a: ChangeValueOfAction =>
val sequences = a.getKeysToSend map { sequence =>
<sequence>{sequence.toString}</sequence>
}
nodeBuffer += <keys>{sequences}</keys>
case a: NavigateToAction =>
nodeBuffer += <url>{a.getUrl}</url>
case a: SwitchToWindowAction =>
nodeBuffer += <window>{a.getWindowName}</window>
case _ =>
}

val topLevelNode = <action>{nodeBuffer}</action>
val stringBuilder = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
val prettyPrinter = new PrettyPrinter(0, 2)
prettyPrinter.formatNodes(topLevelNode, TopScope, stringBuilder)
stringBuilder.toString()
}
def toEntityAttribute(value: String): Action = sutState match {
case Some(state) =>
val node = scala.xml.XML.loadString(value)
val typeNode = getNodeByTag(node, "type")
typeNode.text match {
case "ChangeValueOfAction" =>
val element = getElement(node, state)
val keys = getNodeByTag(node, "keys")
val sequences = keys.child map { c =>
c.text
}
new ChangeValueOfAction(element, sequences.toArray)
case "ClickOnAction" =>
val element = getElement(node, state)
new ClickOnAction(element)
case "NavigateToAction" =>
val urlNode = getNodeByTag(node, "url")
new NavigateToAction(urlNode.text)
case "NavigateBackAction" => new NavigateBackAction
case "NavigateForwardAction" => new NavigateForwardAction
case "NavigateRefreshAction" => new NavigateRefreshAction
case "SwitchToWindowAction" =>
val windowNode = getNodeByTag(node, "window")
new SwitchToWindowAction(windowNode.text)
case _ => throw new RuntimeException("Unknown type.")
}

case None => throw new RuntimeException("We need the SutState to reconstruct the action")
}

private def getNodeByTag(node: Node, tag: String): Node = {
val matchingNodes = node \\ tag
if (matchingNodes.isEmpty) {
throw new RuntimeException(s"Missing node with tag $tag.")
} else {
matchingNodes.head
}
}

private def getElement(node: Node, sutState: SutState): Element = {
val retestId = getNodeByTag(node, "retestId").text
getElementByRetestId(retestId, sutState) match {
case Some(element) => element
case None => throw new RuntimeException(s"Missing element with retestId $retestId")
}
}

private def getElementByRetestId(retestId: String, sutState: SutState): Option[Element] = {
val elements = scala.collection.mutable.Set[Element]()
val iterator = sutState.getRootElements.iterator()
var result: Option[Element] = None

while (iterator.hasNext && result.isEmpty) {
val element = iterator.next()
result = if (element.getRetestId == retestId) {
Some(element)
} else { None }

val nestedIterator = element.getContainedElements.iterator()

while (nestedIterator.hasNext) {
elements += nestedIterator.next()
}
}

result
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
package de.retest.guistatemachine.api.neo4j
import de.retest.recheck.ui.descriptors.SutState
import de.retest.surili.commons.actions.Action
import org.neo4j.ogm.annotation.typeconversion.Convert
import org.neo4j.ogm.annotation.{EndNode, RelationshipEntity, StartNode}
import org.neo4j.ogm.annotation.{EndNode, Index, RelationshipEntity, StartNode}

@RelationshipEntity(`type` = "ACTIONS")
class ActionTransitionEntity(s: SutState, e: SutState, a: Action) extends Entity {

def this() = this(null, null, null)

@Index
@StartNode val start: SutState = s

@Index
@EndNode val end: SutState = e

@Convert(classOf[ActionConverter])
val action: Action = a
@Index
// TODO #19 We need the previous SutState for the conversion back to the action since we rely on the retest ID only to keep the action small.
//@Convert(classOf[ActionConverter])
val actionXML: String = new ActionConverter().toGraphProperty(a)

/// The number of times this action has been executed.
var counter: Int = 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class GuiStateMachineApiNeo4J extends GuiStateMachineApi {

override def getStateMachine(name: String): Option[GuiStateMachine] = stateMachines.get(name)

override def clear(): Unit = stateMachines.keySet foreach { name => // TODO #19 keys can be modified concurrently
override def clear(): Unit = stateMachines.keySet foreach { name => // TODO #19 keys can be modified concurrently. So we might not remove all state machines?
removeStateMachine(name)
} // TODO #19 Removes from disk?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import org.neo4j.ogm.cypher.{ComparisonOperator, Filter}
import scala.collection.immutable.HashMap

class GuiStateMachineNeo4J(var uri: String) extends GuiStateMachine {
implicit val session = Neo4jSessionFactory.getSessionFactory(uri).openSession() // TODO #19 Save the session at some point and close it at some point
implicit val session = Neo4jSessionFactory.getSessionFactory(uri).openSession() // TODO #19 Close the session at some point?

override def getState(sutState: SutState): State = {
Neo4jSessionFactory.transaction {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import org.neo4j.ogm.transaction.Transaction

import scala.collection.concurrent.TrieMap

// TODO #19 Use sessions to modify the state graph.
object Neo4jSessionFactory {
private val sessionFactories = TrieMap[String, SessionFactory]()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ case class StateNeo4J(sutState: SutState, guiStateMachine: GuiStateMachineNeo4J)
val iterator = transitions.iterator()
while (iterator.hasNext) {
val relationship = iterator.next()
val action = relationship.action
val action = new ActionConverter(Some(relationship.start)).toEntityAttribute(relationship.actionXML)
val targetSutState = relationship.end
val counter = relationship.counter
val actionTransitions = if (result.contains(action)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import de.retest.recheck.ui.descriptors.SutState
import org.neo4j.ogm.annotation._
import org.neo4j.ogm.annotation.typeconversion.Convert

// TODO #19 Use this entity and sessions instead of manual transactions.
@NodeEntity
class SutStateEntity(state: SutState) extends Entity {

def this() = this(null)

@Index(unique = true)
@Convert(classOf[SutStateConverter])
val sutState: SutState = state
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package de.retest.guistatemachine.api.neo4j

import java.util

import de.retest.guistatemachine.api.AbstractApiSpec
import de.retest.surili.commons.actions._
import org.scalatest.BeforeAndAfterEach

class ActionConverterSpec extends AbstractApiSpec with BeforeAndAfterEach {
private val rootElement = getRootElement("a", 0)
private val sutState = createSutState(rootElement)
private val cut = new ActionConverter(Some(sutState))

"ActionConverter" should {

"convert ChangeValueOfAction" in {
val list = util.Arrays.asList("foo", "bar", "waa")
val cs = list.toArray(new Array[CharSequence](list.size))
val action = new ChangeValueOfAction(rootElement, cs)

val result = cut.toGraphProperty(action)
result shouldEqual
"""<?xml version="1.0" encoding="UTF-8"?>
|<action><type>ChangeValueOfAction</type><retestId>retestId</retestId><keys><sequence>foo</sequence><sequence>bar</sequence><sequence>waa</sequence></keys></action>
|""".stripMargin

val loadedAction = cut.toEntityAttribute(result)
loadedAction shouldEqual action
}

"convert ClickOnAction" in {
val action = new ClickOnAction(rootElement)

val result = cut.toGraphProperty(action)
result shouldEqual
"""<?xml version="1.0" encoding="UTF-8"?>
|<action><type>ClickOnAction</type><retestId>retestId</retestId></action>
|""".stripMargin

val loadedAction = cut.toEntityAttribute(result)
loadedAction shouldEqual action
}

"convert NavigateToAction" in {
val action = new NavigateToAction("http://google.com")

val result = cut.toGraphProperty(action)
result shouldEqual
"""<?xml version="1.0" encoding="UTF-8"?>
|<action><type>NavigateToAction</type><url>http://google.com</url></action>
|""".stripMargin

val loadedAction = cut.toEntityAttribute(result)
loadedAction shouldEqual action
}

"convert NavigateBackAction" in {
val action = new NavigateBackAction()

val result = cut.toGraphProperty(action)
result shouldEqual
"""<?xml version="1.0" encoding="UTF-8"?>
|<action><type>NavigateBackAction</type></action>
|""".stripMargin

val loadedAction = cut.toEntityAttribute(result)
loadedAction shouldEqual action
}

"convert NavigateForwardAction" in {
val action = new NavigateForwardAction()

val result = cut.toGraphProperty(action)
result shouldEqual
"""<?xml version="1.0" encoding="UTF-8"?>
|<action><type>NavigateForwardAction</type></action>
|""".stripMargin

val loadedAction = cut.toEntityAttribute(result)
loadedAction shouldEqual action
}

"convert NavigateRefreshAction" in {
val action = new NavigateRefreshAction()

val result = cut.toGraphProperty(action)
result shouldEqual
"""<?xml version="1.0" encoding="UTF-8"?>
|<action><type>NavigateRefreshAction</type></action>
|""".stripMargin

val loadedAction = cut.toEntityAttribute(result)
loadedAction shouldEqual action
}

"convert SwitchToWindowAction" in {
val action = new SwitchToWindowAction("test")

val result = cut.toGraphProperty(action)
result shouldEqual
"""<?xml version="1.0" encoding="UTF-8"?>
|<action><type>SwitchToWindowAction</type><window>test</window></action>
|""".stripMargin

val loadedAction = cut.toEntityAttribute(result)
loadedAction shouldEqual action
}
}
}

0 comments on commit 890fc0d

Please sign in to comment.