##############################################################################
# JBoss, Home of Professional Open Source
# Copyright 2008-9, 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
#
# Heuristic Recovery After Delayed 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 and 3 web services
# running in a single JVM but other variants are possible (see below). The
# scenario is as follows (** indicates intercession by a TOAST rule):
#
# AS boots
# Cient starts a WS-AT transaction
# Client invokes web service 1
# Web service 1 registers participant P1
# Client invokes web service 2
# Web service 2 registers participant P2
# Client invokes web service 3
# Web service 3 registers participant P3
# Client commits WS-AT transaction
# Coordinator initiates commit of participant P1
# ** Rule system intercepts commit and crahses JVM
#
# AS reboots
# Recovery system starts after 2 minutes
# Recovery system recreates PREPARED WS-AT transaction coordinator
# Recovery system recreates participant stub for P1
# ** Rule system adds countdown(2) for P1
# Recovery system recreates participant stub for P2
# ** Rule system adds countdown for P2
# Recovery system recreates participant stub for P3
# ** Rule system adds countdown for P3
# Recovery system calls replay of PREPARED transaction
# ** Rule system traces PREPARED replay invocation
#
# Coordinator sends commit to P1
# ** Rule system decrements P1's countdown to 1
#
# P1 replies with committed
# ** Rule system intercepts committed message handler and aborts thread
# (last 2 steps repeated while countdown is active)
#
# Coordinator sends commit to P2
# ** Rule system decrements P2's countdown to 1
#
# P2 replies with committed
# ** Rule system intercepts committed message handler and aborts thread
# (last 2 steps repeated while countdown is active)
#
# Coordinator sends commit to P3
# ** Rule system decrements P3's countdown to 1
#
# P3 replies with committed
# ** Rule system intercepts committed message handler and aborts thread
# (last 2 steps repeated while countdown is active)
#
# Coordinator times out commit and writes heuristic transaction to log
# Recovery system sleeps

# Recovery system restarts after 2 minutes
# Recovery system recreates HEURISTIC WS-AT transaction coordinator
# Recovery system detects existing participant stub for P1
# Recovery system detects existing participant stub for P2
# Recovery system detects existing participant stub for P3
#
# Coordinator sends commit to P1
# ** Rule system decrements P1's countdown to 0 and removes countdown
# P1 replies with committed
# Coordinator sends commit to P2
# ** Rule system decrements P2's countdown to 0 and removes countdown
# P2 replies with committed
# Coordinator sends commit to P3
# ** Rule system decrements P3's countdown to 0 and removes countdown
# P3 replies with committed
# Coordinator clears heuristic log record and copletes 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 can actually be 2, 3 or The
# web service(s), client (i.e. the XTS Service Test which drives
# the test) and coordinator service can optionally be located in separate
# ASs. It possible to use just a single web service and have the client
# register multiple participants. The coordinator AS should crash when
# the client commits. At reboot the rest of the test should run
# automatically and the server should be killed after a the heuristic
# transaction is successfuly replayed.
#
# n.b. this test is not appropriate foir the case where only one participant
# is registered since the coordiantor employs the one phase optimization,
# omitting to log the transaction. In this case the participant should be
# rolled back at restart.
#
# 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. n.b. the rules in
# this script only refer to code executed by the coordinator. If the client
# (the selected XTS Service Test) or the web services are located in another
# AS/JVM then the other JVM does not require a TOAST agent or script.
#
# 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.MultiParticipantPrepareAndCommitTest
# this test invokes a single service registering 3 participant
# the location of service is defined by defining a system property:
#    org.jboss.jbossts.xts.servicetests.ServiceURL
# if this is not set the value used defaults to
#    http://localhost:8080/xtstest/xtsservicetest1
#
# org.jboss.jbossts.xts.servicetests.test.at.MultiServicePrepareAndCommitTest
# this test invokes 3 separate services registering a participant with each service
# the location of service is defined by defining by system properties:
#    org.jboss.jbossts.xts.servicetests.ServiceURL1
#    org.jboss.jbossts.xts.servicetests.ServiceURL2
#    org.jboss.jbossts.xts.servicetests.ServiceURL3
# if these are not set the values used default to
#    http://localhost:8080/xtstest/xtsservicetest1
#    http://localhost:8080/xtstest/xtsservicetest2
#    http://localhost:8080/xtstest/xtsservicetest3
#
# If the client is run in a different AS to the coordinator then the client
# AS needs to be pointed at the coordinator AS. The easiest
# way to do this is to define the system property
#
#    org.jboss.jbossts.xts11.coordinatorURL
#
# to something like
#
#    http://foo.bar.org:8080/ws-c11/soap/ActivationCoordinator
#
# or alternatively to redefine one or more of the component properties
#
#    org.jboss.jbossts.xts11.coordinator.host
#    org.jboss.jbossts.xts11.coordinator.port
#    org.jboss.jbossts.xts11.coordinator.path
#
# (you probably only need to reset the host component)
#
# Expected output
#
# After the first boot the JVM should exit leaving the following in file testlog
#
#   JVM exit
#
# After reboot the JVM should exit leaving output in the following format in file
# testlog. Clearly the ids for the TX/participants may vary and the number of
# countdown completed lines will depend upon the number of participants employed.
#
#   JVM exit
#   replay commit for prepared transaction 7f000001:cebc:496f3b83:6e
#   replay commit for heuristic committed transaction 7f000001:cebc:496f3b83:6e
#   countdown completed for D7f000001:cebc:496f3b83:70
#   countdown completed for D7f000001:cebc:496f3b83:73
#   countdown completed for D7f000001:cebc:496f3b83:76
#   removed committed transaction

#######################################################################
# 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 participant stub
# (CoordinatorEngine) is sent a commit message i.e. immediately
# after a successful prepare. It exits the JVM, simulating a crash.
# The trigger location is set to follow the write of recovered,
# ensuring that the property is avaialable for test in the rule
# condition.

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

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

#######################################################################
# This rule is triggered when a participant stub (CoordinatorEngine) is
# created from details located in the log record. It adds a countdown
# which is tripped each time a commit is tried on the participant.
# While the countdown is active committed messages will be blocked.
# Note that it calls isRecovered() to detect that the stub has been
# recreated from the log. The trigger location is set to follow the
# write of recovered, ensuring that the property is avaialable for
# test in the rule condition.

RULE add coordinator engine countdown
CLASS com.arjuna.wst11.messaging.engines.CoordinatorEngine
METHOD <init>(String, boolean, W3CEndpointReference, boolean, State)
AFTER WRITE recovered
BIND engine:CoordinatorEngine = $0,
     recovered:boolean = engine.isRecovered(),
     identifier:String = engine.getId()
IF recovered
DO debug("adding countdown for " + identifier),
   addCountDown(identifier, 1)
ENDRULE

#######################################################################
# This rule is triggered when a recovered participant stub
# (CoordinatorEngine) is sent a commit message. This happens during
# replay of a prepared TX from the log. It decrements the countdown. First
# time round this takes it from 1 to 0 but leaves it in place. Second
# time round it removes it allowing committed messages to flow.

RULE countdown at 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
   AND
   debug("commit on recovered engine " + identifier)
   AND
   debug("counting down")
   AND
   countDown(identifier)
DO debug("countdown completed for " + identifier),
   traceln("log", "countdown completed for " + identifier)
ENDRULE

#######################################################################
# This rule is triggered when a recovered participant stub
# (CoordinatorEngine) is sent a committed message i.e. in the handler
# thread which responds to a COMMITTED message from a participant.
# If it detects a countdown registered using the participant id it
# forces an early return stopping delivery of the COMMITTED message.

RULE drop committed message
CLASS com.arjuna.wst11.messaging.engines.CoordinatorEngine
METHOD committed(Notification, AddressingProperties, ArjunaContext)
AT ENTRY
BIND engine:CoordinatorEngine = $0,
     recovered:boolean = engine.isRecovered(),
     identifier:String = engine.getId()
IF recovered
   AND
   debug("committed on recovered engine " + identifier)
   AND
   getCountDown(identifier)
DO debug("dropping committed message for " + identifier),
   return
ENDRULE

#######################################################################
# This rule is triggered when the recovery system finds the PREPARED
# transaction 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.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", "replay commit for prepared transaction " + uid)
ENDRULE

#######################################################################
# This rule is triggered when the recovery system finds the COMMITTED
# transaction 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 heuristic committed 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.COMMITTED
DO debug("replaying commit for heuristic committed transaction " + uid),
   traceln("log", "replay commit for heuristic committed transaction " + uid)
ENDRULE

#######################################################################
# This rule is triggered when the recovery system deletes the COMMITTED
# transaction from the log. It prints a message which can be used to
# verify that the test has completed. As a convenience itt also kills
# the JVM to halt the test.

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),
   debug("!!!killing JVM!!!"),
   killJVM()
ENDRULE
