##############################################################################
# JBoss, Home of Professional Open Source
# Copyright 2009, Red Hat Middleware LLC, and individual contributors
# by the @authors tag. See the copyright.txt in the distribution for a
# full listing of individual contributors.
#
# This is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of
# the License, or (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this software; if not, write to the Free
# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA, or see the FSF site: http://www.fsf.org.
#
# @authors Andrew Dinn
#
# AT Subordinate Transaction Crash During Commit
#
# This script automates testing of a specific recovery scenario for the
# JBossTS XTS implementation of the WS-AT 1.1 protocol using orchestration
# rules. The basic scenario employs a coordinator, a subordinate coordinator
# and 2 web services running in a single JVM. The scenario is as follows
# (** indicates intercession by a TOAST rule):
#
# AS boots
# Cient starts a WS-AT transaction T1
# Client invokes web service 1 with register request
# Web service 1 registers participant P1 in T2
# Client requests web service 1 to start a subordinate transaction T2
# Web service 1 creates a subordinate AT transaction
# Web service 1 registers pseudo-participant PP2 in T1
# Client invokes web service 1 with subordinate register command
# Web service 1 resumes T2 and invokes Web Service 2
# Web service 2 registers participant P2 in T2
# Client invokes web service 1 with subordinate register command
# Web service 1 resumes T2 and invokes Web Service 2
# Web service 2 registers participant P3 in T2
#
# Client initiates transaction commit for T1
#
# Coordinator initiates prepare of participant P1
# ** Rule system logs dispatch of prepare to P1
# ** Rule system logs receipt of prepared from P1
# Coordinator initiates prepare of subordinate participant PP1
# ** Rule system logs dispatch of prepare to P1
# Subordinate coordinator initiates prepare of participant P2
# ** Rule system logs dispatch of prepare to P1
# ** Rule system logs receipt of prepared from P1
# Subordinate coordinator initiates prepare of participant P3
# ** Rule system logs dispatch of prepare to P1
# ** Rule system logs receipt of prepared from P1
# ** Rule system logs receipt of prepared from PP1
#
# Coordinator initiates commit of participant P1
# ** Rule system intercepts commit and crashes JVM
#
# AS reboots
# Recovery system starts after 2 minutes
# Recovery system recreates PREPARED WS-AT activity coordinator
# ** Rule system traces create
# Recovery system recreates participant stub for P1
# ** Rule system traces create
# Recovery system recreates participant stub for PP1
# ** Rule system traces create
# Recovery system recreates participant stub for P2
# ** Rule system traces create
# Recovery system recreates participant stub for P3
# ** Rule system traces create
# Recovery system calls replay of PREPARED transaction
# ** Rule system traces PREPARED replay invocation
#
# Coordinator sends commit to P1
# P1 replies with committed
# ** Rule system traces receipt of committed
#
# Coordinator sends commit to PP1
#
# Subordinate coordinator sends commit to P2
# P2 replies with committed
# ** Rule system traces receipt of committed
#
# Subordinate oordinator sends commit to P3
# P3 replies with committed
# ** Rule system traces receipt of committed
#
# PP1 replies with committed
# ** Rule system traces receipt of committed
#
# Coordinator clears log record and completes commit
# ** Rule system detects completed commit and kills JVM
#
# Use of this script
#
# The default way of exercising this test is to deploy the xtstest war
# to a single AS and configure it to run the relevant XTS Service Test.
# The web services and coordinator will be located with the client.
# The number of participants and subordinate participants can actually be
# 2, 3 or more. The  web service(s), client (i.e. the XTS Service Test which
# drives the test) and coordinator and subordinate coordinator service must
# be colocated for this scritp to work (it is possible to distribute all of
# these agents if desired but that woud lrequire more complex script rules).
# The AS should crash when the client commits. At reboot the rest of the test
# should run automatically and the server should be killed after the recovered
# transaction is successfuly replayed.
#
# This script needs to be passed to a TOAST agent in the JVM running
# the coordinator service both at first boot and at reboot. Output will be
# written to file testlog in the working directory of the AS.
#
# XTS Service tests which can operate with this scenario can be selected for
# execution at AS boot by the XTSServiceTestRunnerBean by setting system
# property
#    org.jboss.jbossts.xts.servicetests.XTSServiceTestName
# to the name of a class which will execute the test. This property must
# be defined in the JVM running the AS to which the xtstest war is deployed
# i.e. the client AS. n.b. if the client is colocated with the coordinator then
# this property must be left undefined at AS reboot otherwise the client
# will run again, starting a new TX which may interfere with recovery of the
# crashed TX.
#
# Available tests include:
#
# org.jboss.jbossts.xts.servicetests.test.at.subordinate.MultiParticipantPrepareAndCommitTest
# this test invokes a service to register a participant and start the suborindate
# transaction whose location is defined by defining a system property:
#    org.jboss.jbossts.xts.servicetests.ServiceURL1
# if this is not set the value used defaults to
#    http://localhost:8080/xtstest/xtsservicetest1
# the test forwards a recursive request for registration in the subordinate tarnsaction
# to a second web service whose location is defined by defining a system property:
#    org.jboss.jbossts.xts.servicetests.ServiceURL2
# if this is not set the value used defaults to
#    http://localhost:8080/xtstest/xtsservicetest2
#
# Expected output
#
# After the first boot the JVM should exit leaving the following in file testlog
#
#   prepare received for participant XXXXXX
#   prepared sent for participant XXXXXX
#   prepare received for pseudo participant XXXXXX
#   prepare received for participant XXXXXX
#   prepare received for participant XXXXXX
#   prepared sent for participant XXXXXX
#   prepared sent for participant XXXXXX
#   prepared sent for pseudo participant XXXXXX
#   JVM exit
#
# n.b. there should be at least one prepared message received for each participant
# and in some cases there may be repeat messages
#
# After reboot the JVM should exit leaving output in the following format in file
# testlog.
#
#   prepare sent for participant engine XXXXXX
#   prepare received for participant XXXXXX
#   prepared sent for participant XXXXXX
#   prepared received for participant engine XXXXXX
#   prepare sent for participant engine XXXXXX
#   prepare received for participant XXXXXX
#   prepare called for pseudo participant XXXXXX
#   prepare sent for participant engine XXXXXX
#   prepare received for participant XXXXXX
#   prepared sent for participant XXXXXX
#   prepared received for participant engine XXXXXX
#   prepare sent for participant engine XXXXXX
#   prepare received for participant XXXXXX
#   prepared sent for participant XXXXXX
#   prepared received for participant engine XXXXXX
#   prepare completed for pseudo participant XXXXXX
#   prepare received for participant XXXXXX
#   JVM exit
#   created recovered participant engine XXXXXX
#   created recovered participant engine XXXXXX
#   created recovered participant engine XXXXXX
#   created recovered participant engine XXXXXX
#   created recovered coordinator engine XXXXXX
#   created recovered coordinator engine XXXXXX
#   created recovered coordinator engine XXXXXX
#   created recovered coordinator engine XXXXXX
#   commit sent for participant engine XXXXXX
#   commit received for participant XXXXXX
#   committed sent for participant XXXXXX
#   committed received for participant engine XXXXXX
#   commit sent for participant engine XXXXXX
#   commit received for participant XXXXXX
#   commit called for pseudo participant XXXXXX
#   commit sent for participant engine XXXXXX
#   commit received for participant XXXXXX
#   committed sent for participant XXXXXX
#   committed received for participant engine XXXXXX
#   commit sent for participant engine XXXXXX
#   commit received for participant XXXXXX
#   committed sent for participant XXXXXX
#   committed received for participant engine XXXXXX
#   commit completed for pseudo participant XXXXXX
#   committed sent for participant XXXXXX
#   committed received for participant engine XXXXXX
#   removed committed transaction XXXXXX
#   JVM exit
#
#######################################################################
# This rule opens a file for the trace output during XTS startup
# It will be opened for append at reboot so messages from both runs
# will go to this file
#
RULE open trace file
CLASS org.jboss.jbossts.XTSService
METHOD start()
BIND NOTHING
IF TRUE
DO openTrace("log", "testlog")
ENDRULE

#######################################################################
## rules for first run of AS

#######################################################################
# This rule is triggered when a non-recovered coordinator engine
# (CoordinatorEngine) is sent a commit message. It exits the JVM,
# simulating a crash. The trigger location is on entry

RULE kill JVM at commit
CLASS com.arjuna.wst11.messaging.engines.CoordinatorEngine
METHOD commit
AT ENTRY
BIND engine:CoordinatorEngine = $0,
     recovered:boolean = engine.isRecovered(),
     identifier:String = engine.getId()
IF (NOT recovered)
   AND
   debug("commit on non-recovered coordinator engine " + identifier)
DO traceln("log", "JVM exit"),
   debug("!!!killing JVM!!!"),
   killJVM()
ENDRULE

#######################################################################
# This rule is triggered when a non-recovered coordinator engine
# (CoordinatorEngine) is requested to send a prepare message. It
# traces the call.

RULE trace send participant prepare
CLASS com.arjuna.wst11.messaging.engines.CoordinatorEngine
METHOD prepare
AFTER SYNCHRONIZE
BIND engine:CoordinatorEngine = $0,
     recovered:boolean = engine.isRecovered(),
     identifier:String = engine.getId()
IF NOT recovered
DO debug("prepare sent for coordinator engine " + identifier),
   traceln("log", "prepare sent for coordinator engine " + identifier)
ENDRULE

#######################################################################
# This rule is triggered when a non-recovered coordinator engine
# (CoordinatorEngine) receives a prepared message. It traces the call.

RULE trace receive participant prepared
CLASS com.arjuna.wst11.messaging.engines.CoordinatorEngine
METHOD prepared(Notification, AddressingProperties, ArjunaContext)
AT ENTRY
BIND engine:CoordinatorEngine = $0,
     recovered:boolean = engine.isRecovered(),
     identifier:String = engine.getId()
IF NOT recovered
DO debug("prepared received for coordinator engine " + identifier),
   traceln("log", "prepared received for coordinator engine " + identifier)
ENDRULE

#######################################################################
# This rule is triggered when a non-recovered participant
# (ParticipantEngine) receives a prepare message. It
# traces the call.

RULE trace receive participant prepare
CLASS com.arjuna.wst11.messaging.engines.ParticipantEngine
METHOD prepare
AFTER SYNCHRONIZE
BIND engine:ParticipantEngine = $0,
     recovered:boolean = engine.isRecovered(),
     identifier:String = engine.getId()
IF NOT recovered
DO debug("prepare received for participant " + identifier),
   traceln("log", "prepare received for participant " + identifier)
ENDRULE

#######################################################################
# This rule is triggered when a non-recovered participant
# (ParticipantEngine) is requested to send a prepared message. It
# traces the call.

RULE trace send participant prepared
CLASS com.arjuna.wst11.messaging.engines.ParticipantEngine
METHOD sendPrepared(boolean)
AT INVOKE sendPrepared
BIND engine:ParticipantEngine = $0,
     recovered:boolean = engine.isRecovered(),
     identifier:String = engine.getId()
IF NOT recovered
DO debug("prepared sent for participant " + identifier),
   traceln("log", "prepared sent for participant " + identifier)
ENDRULE

#######################################################################
# This rule is triggered when a subordinate pseudo participant
# (SubordinateDurable2PCStub) is requested to send a prepared message. It
# traces the call.

RULE trace subordinate participant stub prepare
CLASS com.arjuna.wst11.stub.SubordinateDurable2PCStub
METHOD prepare
AT ENTRY
BIND NOTHING
IF TRUE
DO debug("prepare called for pseudo-participant " + $0),
   traceln("log", "prepare called for pseudo-participant " + $0)
ENDRULE

#######################################################################
# This rule is triggered when a subordinate pseudo participant
# (SubordinateDurable2PCStub) is requested to send a prepared message. It
# traces the call.

RULE trace subordinate participant stub prepare
CLASS com.arjuna.wst11.stub.SubordinateDurable2PCStub
METHOD prepare
AFTER INVOKE prepare
BIND NOTHING
IF TRUE
DO debug("prepare completed for pseudo-participant " + $0),
   traceln("log", "prepare completed for pseudo-participant " + $0)
ENDRULE

#######################################################################
## rules for reboot run of AS

#######################################################################
# This rule is triggered when a coordinator engine (CoordinatorEngine)
# is created from details located in the log record. It traces the
# create operation. The trigger location is at entry but the rule
# should only be triggered after calling the super constructor
RULE trace coordinator engine create
CLASS com.arjuna.wst11.messaging.engines.CoordinatorEngine
METHOD <init>(String, boolean, W3CEndpointReference, boolean, State)
AT ENTRY
BIND identifier = $1,
     recovered=$4
IF recovered
DO debug("created recovered coordinator engine " + identifier),
   traceln("log", "created recovered coordinator engine " + identifier)
ENDRULE

#######################################################################
# This rule is triggered when a participant (ParticipantEngine)
# is created from details located in the log record. It traces the
# create operation. The trigger location is at entry but the rule
# should only be triggered after calling the super constructor

RULE trace participant create
CLASS com.arjuna.wst11.messaging.engines.ParticipantEngine
METHOD <init>(Participant, String, State, W3CEndpointReference, boolean)
AT ENTRY
BIND identifier = $2,
     recovered=$5
IF recovered
DO debug("created recovered participant engine " + identifier),
   traceln("log", "created recovered participant engine " + identifier)
ENDRULE

#######################################################################
# This rule is triggered when a recovered coordinator engine
# (CoordinatorEngine) is requested to send a commit message. This
# happens during replay of a prepared TX from
# the log. It traces the call.

RULE trace send recovered participant commit
CLASS com.arjuna.wst11.messaging.engines.CoordinatorEngine
METHOD commit
AFTER SYNCHRONIZE
BIND engine:CoordinatorEngine = $0,
     recovered:boolean = engine.isRecovered(),
     identifier:String = engine.getId()
IF recovered
DO debug("send commit for recovered coordinator engine " + identifier),
   traceln("log", "send commit for recovered coordinator engine " + identifier)
ENDRULE

#######################################################################
# This rule is triggered when a recovered coordinator engine
# (CoordinatorEngine) receives a committed message. This
# happens during replay of a prepared TX from
# the log. It traces the call.

RULE trace receive recovered participant committed
CLASS com.arjuna.wst11.messaging.engines.CoordinatorEngine
METHOD committed
AFTER SYNCHRONIZE
BIND engine:CoordinatorEngine = $0,
     recovered:boolean = engine.isRecovered(),
     identifier:String = engine.getId()
IF recovered
DO debug("received committed for recovered coordinator engine " + identifier),
   traceln("log", "received committed for recovered coordinator engine " + identifier)
ENDRULE

#######################################################################
# This rule is triggered when a recovered participant engine
# (ParticipantEngine) is requested to send a committed message. This
# happens during replay of a prepared TX from
# the log. It traces the call.

RULE trace send recovered participant committed
CLASS com.arjuna.wst11.messaging.engines.ParticipantEngine
METHOD sendCommitted
AFTER INVOKE sendCommitted
BIND engine:ParticipantEngine = $0,
     recovered:boolean = engine.isRecovered(),
     identifier:String = engine.getId()
IF recovered
DO debug("send committed for recovered participant engine " + identifier),
   traceln("log", "send committed for recovered participant engine " + identifier)
ENDRULE

#######################################################################
# This rule is triggered when a recovered coordinator engine
# (ParticipantEngine) receives a commit message. This
# happens during replay of a prepared TX from
# the log. It traces the call.

RULE trace receive recovered participant commit
CLASS com.arjuna.wst11.messaging.engines.ParticipantEngine
METHOD commit
AFTER SYNCHRONIZE
BIND engine:ParticipantEngine = $0,
     recovered:boolean = engine.isRecovered(),
     identifier:String = engine.getId()
IF recovered
DO debug("received commit for recovered participant engine " + identifier),
   traceln("log", "received commit for recovered participant engine " + identifier)
ENDRULE

#######################################################################
# This rule is triggered when a subordinate pseudo participant
# (SubordinateDurable2PCStub) is requested to send a commit message. It
# traces the call.

RULE trace subordinate participant stub commit
CLASS com.arjuna.wst11.stub.SubordinateDurable2PCStub
METHOD commit
AT ENTRY
BIND NOTHING
IF TRUE
DO debug("commit called for pseudo-participant " + $0),
   traceln("log", "commit called for pseudo-participant " + $0)
ENDRULE

#######################################################################
# This rule is triggered when a subordinate pseudo participant
# (SubordinateDurable2PCStub) forwards a commit message to its subordinate
# coordinator during recovery. It traces the call.

RULE trace subordinate participant stub commit forward
CLASS com.arjuna.wst11.stub.SubordinateDurable2PCStub
METHOD commit
AT INVOKE commit 2
BIND NOTHING
IF TRUE
DO debug("forwarding commit for pseudo-participant " + $0 + " to subordinate coordinator"),
   traceln("log", "forwarding commit for pseudo-participant " + $0 + " to subordinate coordinator")
ENDRULE

#######################################################################
# This rule is triggered when a subordinate pseudo participant
# (SubordinateDurable2PCStub) throws an error from its commit method
# because the subordinate coordinator has not yet been recovered. It
# traces the throw.

RULE trace subordinate participant stub commit with no coordinator
CLASS com.arjuna.wst11.stub.SubordinateDurable2PCStub
METHOD commit
AT THROW
BIND NOTHING
IF TRUE
DO debug("throw during commit for pseudo-participant " + $0 + " with no subordinate coordinator"),
   traceln("log", "throw during commit for pseudo-participant " + $0 + " with no subordinate coordinator")
ENDRULE

#######################################################################
# This rule is triggered when a subordinate pseudo participant
# (SubordinateDurable2PCStub) throws an error from its commit method
# because the subordinate coordinator has not yet been activated. It
# traces the throw.


RULE trace subordinate participant stub commit commit with unactivated coordinator
CLASS com.arjuna.wst11.stub.SubordinateDurable2PCStub
METHOD commit
AT THROW 2
BIND NOTHING
IF TRUE
DO debug("throw during commit for pseudo-participant " + $0 + " with unactivated subordinate coordinator"),
   traceln("log", "throw during commit for pseudo-participant " + $0 + " with unactivated subordinate coordinator")
ENDRULE

#######################################################################
# This rule is triggered when a subordinate pseudo participant
# (SubordinateDurable2PCStub) returns from sending a commit message. It
# traces the call.

RULE trace subordinate participant stub commit return
CLASS com.arjuna.wst11.stub.SubordinateDurable2PCStub
METHOD commit
AT EXIT
BIND NOTHING
IF TRUE
DO debug("commit completed for pseudo-participant " + $0),
   traceln("log", "commit completed for pseudo-participant " + $0)
ENDRULE

#######################################################################
# This rule is triggered when a subordinate coordinator
# (SubordinateCoordinator) is requested to perform a commit. It
# traces the call.

RULE trace subordinate coordinator commit
CLASS com.arjuna.mwlabs.wscf.model.twophase.arjunacore.subordinate.SubordinateCoordinator
METHOD commit
AT ENTRY
BIND NOTHING
IF TRUE
DO debug("commit called for subordinate coordinator " + $0),
   traceln("log", "commit called for subordinate coordinator " + $0)
ENDRULE

#######################################################################
# This rule is triggered when a subordinate coordinator
# (SubordinateCoordinator) is requested to perform a commit. It
# traces the call.

RULE trace subordinate coordinator commit
CLASS com.arjuna.mwlabs.wscf.model.twophase.arjunacore.subordinate.SubordinateCoordinator
METHOD commit
AT EXIT
BIND NOTHING
IF TRUE
DO debug("commit completed for subordinate coordinator " + $0),
   traceln("log", "commit completed for subordinate coordinator " + $0)
ENDRULE

#######################################################################
# This rule is triggered when the recovery system finds a PREPARED
# parent activity in the log and reruns the phase 2 commit operation.
# It prints a message which can be used to verify that the test has
# progressed as expected and creates a countdown used to detect exit of
# both transactions

RULE trace prepared replay
CLASS org.jboss.jbossts.xts.recovery.coordinator.at.RecoverACCoordinator
METHOD replayPhase2
AT INVOKE phase2Commit
BIND coordinator = $0,
     uid : Uid = coordinator.identifier(),
     status : int = coordinator.status()
IF (status == com.arjuna.ats.arjuna.coordinator.ActionStatus.PREPARED)
     OR
     (status == com.arjuna.ats.arjuna.coordinator.ActionStatus.COMMITTING)
DO debug("replaying commit for prepared transaction " + uid),
   traceln("log", "replaying commit for prepared transaction " + uid),
   createCountDown("exit countdown", 1)
ENDRULE

#######################################################################
# This rule is triggered when the recovery system finds a PREPARED
# subordinate activity in the log and reruns the phase 2 commit operation.
# It prints a message which can be used to verify that the test has
# progressed as expected

RULE trace prepared replay
CLASS org.jboss.jbossts.xts.recovery.coordinator.at.RecoverSubordinateCoordinator
METHOD replayPhase2
AT INVOKE removeRecoveredCoordinator
BIND coordinator = $0,
     uid : Uid = coordinator.identifier(),
     status : int = coordinator.status()
IF (status == com.arjuna.ats.arjuna.coordinator.ActionStatus.PREPARED)
     OR
     (status == com.arjuna.ats.arjuna.coordinator.ActionStatus.COMMITTING)
DO debug("replaying commit for prepared subordinate transaction " + uid),
   traceln("log", "replaying commit for prepared subordinate transaction " + uid)
ENDRULE

#######################################################################
# This rule is triggered when the recovery system deletes the COMMITTED
# activity from the log. It prints a message which can be used to
# verify that the test has completed.

RULE trace remove committed state
CLASS com.arjuna.ats.arjuna.coordinator.BasicAction
METHOD updateState
AFTER CALL remove_committed
BIND action : BasicAction = $0,
     uid  = action.get_uid()
IF TRUE
DO traceln("log", "removed committed transaction " + uid),
   debug("removed committed transaction " + uid)
ENDRULE

#######################################################################
# activity from the log for the second time. It ensures that the JVM
# exits. n.b. it waits for the second delete because both the subordinate
# tx and the parent tx need to be deleted.

RULE trace remove committed state and exit JVM
CLASS com.arjuna.ats.arjuna.coordinator.BasicAction
METHOD updateState
AFTER CALL remove_committed
BIND action : BasicAction = $0,
     uid  = action.get_uid()
IF countDown("exit countdown")
DO traceln("log", "JVM exit after removing both transactions"),
   debug("JVM exit after removing both transactions"),
   killJVM()
ENDRULE
