##############################################################################
# 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
#
# Participant Recovery with Remote Coordinator
#
# 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 client (XTS Service Test program)
# and 2 web services located in one JVM/AS and a coordinator located in another
# JVM/AS but it is possible for the lcinet and web services to be lcoated in
# separate JVMs/ASs. The scenario is as follows (note ** AS<n> indicates
# intercession by a TOAST rule executed in a specific AS):
#
# AS1 boots
# AS2 boots
# AS1 Client starts a WS-AT transaction talking to coordinator in AS2
# AS1 Client invokes web service 1
# AS1 Web service 1 registers as participant P1
# AS1 Client invokes web service 2
# AS1 Web service 2 registers as participant P2
# AS1 Client commits WS-AT transaction
#
# AS2 Coordinator initiates commit of participant P1
# AS2 Coordinator sends commit to P1
#
# AS1 P1 receives commit
# ** AS1 Rule system intercepts commit and crashes JVM
#
# AS2 Coordinator attempts to resend message
# ** AS2 Rule system suspends coordinator waiting on incoming prepared
#    message from P1
#    (commit messages continue to be resent in the meantime)
#
# AS1 reboots
# AS1 Recovery system starts after 2 minutes
# AS1 Recovery system recreates PREPARED WS-AT participant P1
# ** AS1 Rule system traces recreate
# AS1 Participant P1 resends prepared
# AS1 Participant P1 receives commit for P1
# AS1 Participant P1 sends committed
#
# AS2 Participant stub receives committed message for P1
# ** AS2 Rule system signals coordinator clearing wait
# ** AS2 rule system traces receipt of committed message for P1
#
# AS2 Coordinator detects committed from P1
# AS2 Coordinator sends commit for P2
# AS2 Coordinator receives committed from P2
# AS2 Coordinator completes transaction
# ** AS2 Rule system traces successful completion
#
# Use of this script
#
# The default way of exercising this test is to deploy the xtstest war
# to AS1 and configure it to run the relevant XTS Service Test. It is
# actually possible to use 1, 2, 3 or more participants and multiple
# participants may be registered by a single web service or by independent
# web services. If required the web service(s) may be located in a a separate
# AS to the one running the XTS Service test.
#
# The test should crash AS1 at the point of commit. At AS1 reboot the rest of
# the test should run automatically. AS2 should be killed after the
# transaction is successfuly terminated. Still need to work out how to
# automatically terminate AS1 automatically but exit of AS2 is a signal
# that AS1 can be killed.
#
# !!! N.B. this test currently fails because JBossWS Native fails to cope
# !!! with requests for services while they are bootstrapping
#
# The JVMs hosting AS1 and AS2 both need to be configured to run a TOAST
# agent supplied with this script. If the web service(s) is relocated
# to AS3 then it needs to be configured to run the TOAST agent instead of
# AS1 (rules only refer to participant and coordinator code, not to the
# XTS service test code).
#
# The client AS (AS1) needs to be pointed at the coordinator AS (AS2). 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)
#
# 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 AS1, the client AS. n.b. if the client is colocated with
# the seb service(s) then this property must be left undefined at AS1 reboot
# otherwise the client will run again, starting a new TX which may interfere
# with recovery of the crashed participants.
#
# Available tests include:
#
# org.jboss.jbossts.xts.servicetests.test.at.SingleParticipantPrepareAndCommitTest
# this test invokes a single web service registering 1 participant
# the location of service is defined by defining a system property:
#    org.jboss.jbossts.xts.servicetests.ServiceURL1
# which should be set to something like
#    http://baz.bar.org:8080/xtstest/xtsservicetest1
#
# org.jboss.jbossts.xts.servicetests.test.at.MultiParticipantPrepareAndCommitTest
# this test invokes a single service registering 3 participants
# the location of service is defined by defining a system property:
#    org.jboss.jbossts.xts.servicetests.ServiceURL1
# which should be set to something like
#    http://baz.bar.org:8080/xtstest/xtsservicetest1
#
# org.jboss.jbossts.xts.servicetests.test.at.MultiServicePrepareAndCommitTest
# this test invokes 3 separate services registering a participant for 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
# which should be set to something like
#    http://mumble.bar.org:8080/xtstest/xtsservicetest1
#    http://mumble.bar.org:8080/xtstest/xtsservicetest2
#    http://mumble.bar.org:8080/xtstest/xtsservicetest3
# if you are feeling really adventurous you can host the services
# in different ASs
#    http://mumble.bar.org:8080/xtstest/xtsservicetest1
#    http://grumble.bar.org:8080/xtstest/xtsservicetest1
#    http://bletch.bar.org:8080/xtstest/xtsservicetest1
#
# Expected output AS1
# -------------------
#
# After the first boot AS1 should exit leaving the following in file testlog1
#
#   JVM exit
#
# After reboot AS1 should add output in the following format in file testlog1.
# Clearly the ids for the TX/participants may vary and the number of
# ??? 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
#
# Expected output AS2
# -------------------
# ???

######################################################################
# AS1 Rule set
#######################################################################
# 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("log1", "testlog1")
ENDRULE


#######################################################################
# This rule is triggered in the AS1 when a non-recovered WS-AT
# participant receives a commit message. It causes the JVM to crash.
# The trigger point is the start of the method.
RULE crash unrecovered participant at commit
CLASS com.arjuna.wst11.messaging.engines.ParticipantEngine
METHOD commit
AT ENTRY
BIND engine:ParticipantEngine = $0,
     recovered:boolean = engine.isRecovered(),
     identifier:String = engine.getId()
IF (NOT recovered)
   AND
   debug("commit on non-recovered participant engine " + identifier)
DO debug("!!!killing JVM!!!"),
   killJVM()
ENDRULE

#######################################################################
# This rule is triggered in the AS1 when a recovered WS-AT participant
# is recreated from the log. It traces the call to allow success of the
# test to be detected.
# The trigger point is the end of the constructor so that the rule can
# safely read all the fields.

RULE trace recovered participant recreate
CLASS com.arjuna.wst11.messaging.engines.ParticipantEngine
METHOD <init>(Participant, String, State, W3CEndpointReference, boolean)
AFTER WRITE persisted
BIND engine:ParticipantEngine = $0,
     recovered:boolean = engine.isRecovered(),
     identifier:String = engine.getId()
IF recovered
DO debug("recreated recovered participant engine " + identifier)
ENDRULE


######################################################################
# AS2 Rule set
#######################################################################
# This rule opens a file for the trace output during XTS startup

RULE open trace file
CLASS org.jboss.jbossts.XTSService
METHOD start()
BIND NOTHING
IF TRUE
DO openTrace("log2", "testlog2")
ENDRULE

######################################################################
# This rule is triggered in AS2 when a participant stub
# (CoordinatorEngine) first posts a commit message to the participant.
# The coordinator thread suspends until a committed message is received
# from the participant. This ensures that it waits until AS1
# has restarted allowing the transaction to then complete. The
# condition tests whether a flag with key "firstCommit" has been set.
# By default this is clear so the condition is true first time the
# rule is called. The action sets te flag causing the condition to
# evaluate to false on subsequent commit calls.
# The trigger point is just before calling waitForState.

RULE suspend coordinator after sending first commit
CLASS com.arjuna.wst11.messaging.engines.CoordinatorEngine
METHOD commit
LINE 330
BIND engine:CoordinatorEngine = $0,
     identifier:String = engine.getId()
IF (NOT flagged("firstCommit"))
DO debug("coordinator waiting for participant to resend prepare" + identifier),
   flag("firstCommit"),
   waitFor(identifier)
ENDRULE

#######################################################################

# This rule is triggered when a committed message is received from P1.
# This indicates that the participant has been recreated and
# successfully responded to a commit message after reboot of AS1. The
# rule uses a call to signal to wake up the coordinator which was
# suspended when the first commit was sent. This call only returns true
# if the identifier is for P1 and the coordinator has not already been
# signalled. Note that the call to signal is made in the condition to
# avoid a race between threads handling successive committed messages.
# If instead the condition called waiting() and the action called
# signal() then, depending upon scheduling, both threads might find the
# condition to be true before one of them could execute signal().
# Although this would be harmless in this case it could be significant
# when using other rules.
# The triogger point is at the sttart of the method.

RULE signal waiting coordinator
CLASS com.arjuna.wst11.messaging.engines.CoordinatorEngine
METHOD committed
AT ENTRY
BIND engine : CoordinatorEngine = $0,
     identifier:String = engine.getId()
IF debug("received committed for participant " + identifier)
   AND
   signalWake(identifier)
DO debug("signalled coordinator waiting on " + identifier)
ENDRULE
