package org.jboss.jbossts.xts.servicetests.service;
import org.jboss.jbossts.xts.servicetests.service.participant.*;
import org.jboss.jbossts.xts.servicetests.client.XTSServiceTestClient;
import org.jboss.jbossts.xts.servicetests.generated.CommandsType;
import org.jboss.jbossts.xts.servicetests.generated.ResultsType;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import com.arjuna.wst11.BAParticipantManager;
import com.arjuna.mw.wst.TxContext;
import com.arjuna.mw.wst11.*;
import com.arjuna.wst.SystemException;
import javax.xml.ws.WebServiceException;
import javax.xml.namespace.QName;
/**
* A class which provides the ability to interpret XTS service test commands. Each of the XTS service test
* classes imnplementing a specific test extends this class so that it can drive the test from a command
* script. Each of the XTS service test endpoints employs an instance of this class to process comands
* which are dispatched to it either from one of the test class instances or recursively via another
* intermediate service.
*
* The interpreter executes a list of Strings which represents one or more commands each with its own
* sequence of associated arguments. It returns a list of strings indicating the outcome of processing
* the commands. It throws an exception if command processing fails.
*
* A simple command comprises a list of Strings headed by one of a fixed set of command keywords followed by
* zero or more argument strings. Command arguments are either simple string literals or bound variable references.
* A variable reference is an alphanumeric variable name bracketed by '{' and '}' characters. The interpreter
* resolves this to a string value by looking up the variable in its binding list and uses this vale as the
* argument for th command. It is possible to seed the binding list with default values before processing a
* sequence of commands. Alternatively, bindings may be added during execution of block commands.
*
* A block commands comprises a list of Strings headed by the keyword "block" and terminated by the keyword
* "endblock". Intervening elements of the list comprise simple commands separated either by "next" keywords
* or by bind commands terminated with a "next" keyword. A bind commands is a list of strings headed by keyword
* "bind" and succeeded by a sequence of bind operations. A bind operation operates on the results returned from
* the last executed command and comes in one of three formats:
* <ul>
* <li>set var idx
* <li>check idx value/{var}"
* <li>output idx
* </ul>
* A set operation binds a variable to the nth result returned in the results list of the previously executed
* command. A check operation checks that the nth result returned in the results list of the previously executed
* command has a specific value, throwing an exception if not. An output operation appends the nth result returned
* in the results list of the previously executed command to the results list for the block command.
*
* The command syntax is defined as follows:
*
* <pre>
* command ::= simple_command | block_command ==> resultList
*
* block_command ::= "block" command (block_trailing)* "endblock"
*
* block_trailing ::= (bindings_command)? "next" command
*
* bindings_command ::= "bindings" (binding_operation)+ "next"
*
* binding_operation ::= "bind" <alphanum> <num> |
* "check" <num> <alphanum> |
* "output" <num>
*
* simple_command :=
* "enlistDurable" (ATParticipantInstruuction)* ==> DurableId ;
* "enlistVolatile" (ATParticipantInstruuction)* ==> VolatileId ;
* "enlistCoordinatorCompletion" (BACoordinatorCompletionInstruuction)* ==> BACoordinatorCompletionId ;
* "enlistParticipantCompletion" (BAParticipantCompletionInstruuction)* ==> BAParticipantCompletionId ;
* "addCommands" ((DurableId | VolatileId) (ATParticipantInstruuction)* ==> "ok" |
* ((BAParticipantCompletionId | BACoordinatorCompletionId) BACoordinatorCompletionInstruuction)*) ==> "ok" ;
* "fail" (BAParticipantCompletionId | BACoordinatorCompletionId) ==> "ok" ;
* "exit" (BAParticipantCompletionId | BACoordinatorCompletionId) ==> "ok" ;
* "cannotComplete" BAParticipantCompletionId ==> "ok" ;
* "completed" BAParticipantCompletionId ==> "ok" ;
* "serve" URL command ==> resultsList
* "subtransaction" ==> subtransactionId ;
* "subactivity" ==> subactivityId ;
* "subtransactioncommands" subtransactionId serviceURL (command) ;
* "subactivitycommands" subactivityId serviceURL (command)
*
* ATParticipantInstruuction ::=
* "prepare" ;
* "prepareSystemException" ;
* "prepareWrongStateException" ;
* "commit" ;
* "commitSystemException" ;
* "commitWrongStateException"
*
* BAParticipantCompletionInstruuction ::=
* "close" ;
* "closeSystemException" ;
* "closeWrongStateException" ;
* "cancel" ;
* "cancelSystemException" ;
* "cancelWrongStateException" ;
* "cancelFaultedException" ;
* "compensate" ;
* "compensateSystemException" ;
* "compensateWrongStateException" ;
* "compensateFaultedException"
*
* BAParticipantCompletionInstruuction ::=
* "complete" ;
* "completeSystemException" ;
* "completeWrongStateException" ;
*
* DurableId ::=
* "org.jboss.jbossts.xts.servicetests.DurableTestParticipant.<num>"
* VolatileId ::=
* "org.jboss.jbossts.xts.servicetests.VolatileTestParticipant.<num>"
* BACoordinatorCompletionId ::=
* "org.jboss.jbossts.xts.servicetests.CoordinatorCompletionTestParticipant.<num>"
* BAParticipantCompletionId ::=
* "org.jboss.jbossts.xts.servicetests.ParticipantCompletionTestParticipant.<num>"
*
* subtransactionId is a String of the form
* "transaction.at.<num>"
*
* subactivityId is a String of the form
* "transaction.ba.<num>"
*</pre>
* where
*
* <num> is a sequence of base 10 digits and <alphanum> is a sequence of
* alphanumeric characters
*
* serviceURL is a URL identifying an instance of the test service to which commands are to be
* recursively dispatched within the relevant subtransaction or subactivity
*/
public class XTSServiceTestInterpreter
{
/// public API
/**
* lookup (or create and index) then return a service implementation keyed by a specific servlet path
* @param servletPath the key used to identify a service obtained from an HTTP request's servlet path
* @return the associated service instance
*/
public static synchronized XTSServiceTestInterpreter getService(String servletPath)
{
XTSServiceTestInterpreter service = serviceMap.get(servletPath);
if (service == null) {
service = new XTSServiceTestInterpreter();
serviceMap.put(servletPath, service);
}
return service;
}
/**
* simple command interpreter which executes the commands in the command list, inserting the
* corresponding results in the results list. n.b. this method should only ever be invoked
* from within an AT or BA transaction.
*
* @param commandList a list of command strings
* @param resultsList a list into which results strings are to be inserted
*/
public void processCommands(List<String> commandList, List<String> resultsList)
throws WebServiceException
{
HashMap<String, String> bindings = new HashMap<String, String>(defaultBindings);
processCommands(commandList, resultsList, bindings);
}
/**
* add a binding to the default binding set used to supply command parameters
* @param var
* @param val
*/
public void addDefaultBinding(String var, String val)
{
val = replaceLocalhostIfIPv6AndUrl(val);
defaultBindings.put(var, val);
}
public String replaceLocalhostIfIPv6AndUrl(String url)
{
if (isIPv6())
{
url = url.replace("http://localhost", "http://[::1]");
}
return url;
}
private boolean isIPv6() {
try {
if (InetAddress.getLocalHost() instanceof Inet6Address || System.getenv("IPV6_OPTS") != null)
return true;
} catch (final UnknownHostException uhe) {
}
return false;
}
/// overrideable methods
/**
* set up a specific service instance with all the values it requires
*/
protected XTSServiceTestInterpreter()
{
participantMap = new HashMap<String, ScriptedTestParticipant>();
managerMap = new HashMap<String, BAParticipantManager>();
subordinateTransactionMap = new HashMap<String, TxContext>();
subordinateActivityMap = new HashMap<String, TxContext>();
defaultBindings = new HashMap<String, String>();
client = null;
}
/// implementation of the interpreter functionality
/**
* simple command interpreter which executes the commands in the command list, inserting the
* corresponding results in the results list and using the supplied bindings list
* to provide values for any parameters supplied in the commands and to bind any results
* obtained by executing the commands
*
* @param commandList a list of command strings
* @param resultsList a list into which results strings are to be inserted
* @param bindings a list of bound variables to be substituted into commands
* or updated with new bindings
*/
private void processCommands(List<String> commandList, List<String> resultsList, HashMap<String, String> bindings)
throws WebServiceException
{
int size = commandList.size();
if (size == 0) {
throw new WebServiceException("empty command list");
}
String command = commandList.remove(0);
size--;
// check against each of the possible commands
if (command.equals("block")) {
// we don't bind the command block immediately since bindings may be produced
// by intermediate bind commands
processCommandBlock(commandList, resultsList, bindings);
} else {
int idx = 0;
if (command.equals("enlistDurable")) {
// enlistment commands
bindCommands(commandList, bindings);
String id = participantId("DurableTestParticipant");
DurableTestParticipant participant = new DurableTestParticipant(id);
TransactionManager txman = TransactionManagerFactory.transactionManager();
try {
txman.enlistForDurableTwoPhase(participant, id);
} catch (Exception e) {
throw new WebServiceException("enlistDurable failed ", e);
}
for (idx = 0; idx < size; idx++) {
participant.addCommand(commandList.get(idx));
}
participantMap.put(id, participant);
resultsList.add(id);
} else if (command.equals("enlistVolatile")) {
bindCommands(commandList, bindings);
String id = participantId("VolatileTestParticipant");
VolatileTestParticipant participant = new VolatileTestParticipant(id);
TransactionManager txman = TransactionManagerFactory.transactionManager();
try {
txman.enlistForVolatileTwoPhase(participant, id);
} catch (Exception e) {
throw new WebServiceException("enlistVolatile failed ", e);
}
for (idx = 0; idx < size; idx++) {
participant.addCommand(commandList.get(idx));
}
participantMap.put(id, participant);
resultsList.add(id);
} else if (command.equals("enlistCoordinatorCompletion")) {
bindCommands(commandList, bindings);
String id = participantId("CoordinatorCompletionParticipant");
CoordinatorCompletionTestParticipant participant = new CoordinatorCompletionTestParticipant(id);
BusinessActivityManager baman = BusinessActivityManagerFactory.businessActivityManager();
try {
BAParticipantManager partMan;
partMan = baman.enlistForBusinessAgreementWithCoordinatorCompletion(participant, id);
managerMap.put(id, partMan);
} catch (Exception e) {
throw new WebServiceException("enlistCoordinatorCompletion failed ", e);
}
for (idx = 0; idx < size; idx++) {
participant.addCommand(commandList.get(idx));
}
participantMap.put(id, participant);
resultsList.add(id);
} else if (command.equals("enlistParticipantCompletion")) {
bindCommands(commandList, bindings);
String id = participantId("ParticipantCompletionParticipant");
ParticipantCompletionTestParticipant participant = new ParticipantCompletionTestParticipant(id);
BusinessActivityManager baman = BusinessActivityManagerFactory.businessActivityManager();
try {
BAParticipantManager partMan;
partMan = baman.enlistForBusinessAgreementWithParticipantCompletion(participant, id);
managerMap.put(id, partMan);
} catch (Exception e) {
throw new WebServiceException("enlistParticipantCompletion failed ", e);
}
for (idx = 0; idx < size; idx++) {
participant.addCommand(commandList.get(idx));
}
participantMap.put(id, participant);
resultsList.add(id);
} else if (command.equals("addCommands")) {
// add extra commands to a participant script
bindCommands(commandList, bindings);
String id = commandList.remove(idx);
size--;
ScriptedTestParticipant participant = participantMap.get(id);
if (participant != null) {
for (idx = 0; idx < size; idx++)
{
participant.addCommand(commandList.get(idx));
}
resultsList.add("ok");
} else {
throw new WebServiceException("addCommands failed to find participant " + id);
}
} else if (command.equals("exit")) {
// initiate BA manager activities
bindCommands(commandList, bindings);
String id = commandList.remove(idx);
size--;
ScriptedTestParticipant participant = participantMap.get(id);
if (participant != null ) {
if (participant instanceof ParticipantCompletionTestParticipant) {
ParticipantCompletionTestParticipant baparticipant = (ParticipantCompletionTestParticipant)participant;
BAParticipantManager manager = managerMap.get(id);
try {
manager.exit();
} catch (Exception e) {
throw new WebServiceException("exit " + id + " failed with exception " + e);
}
resultsList.add("ok");
} else {
throw new WebServiceException("exit invalid participant type " + id);
}
} else {
throw new WebServiceException("exit unknown participant " + id);
}
} else if (command.equals("completed")) {
bindCommands(commandList, bindings);
String id = commandList.remove(idx);
size--;
ScriptedTestParticipant participant = participantMap.get(id);
if (participant != null ) {
if (participant instanceof ParticipantCompletionTestParticipant) {
ParticipantCompletionTestParticipant baparticipant = (ParticipantCompletionTestParticipant)participant;
BAParticipantManager manager = managerMap.get(id);
try {
manager.completed();
resultsList.add("ok");
} catch (Exception e) {
throw new WebServiceException("completed " + id + " failed with exception " + e);
}
} else {
throw new WebServiceException("completed invalid participant type " + id);
}
} else {
throw new WebServiceException("completed unknown participant " + id);
}
} else if (command.equals("fail")) {
bindCommands(commandList, bindings);
String id = commandList.remove(idx);
size--;
ScriptedTestParticipant participant = participantMap.get(id);
if (participant != null ) {
if (participant instanceof ParticipantCompletionTestParticipant) {
ParticipantCompletionTestParticipant baparticipant = (ParticipantCompletionTestParticipant)participant;
BAParticipantManager manager = managerMap.get(id);
QName qname = new QName("http://jbossts.jboss.org/xts/servicetests/", "fail");
try {
manager.fail(qname);
resultsList.add("ok");
} catch (Exception e) {
throw new WebServiceException("fail " + id + " failed with exception " + e);
}
} else {
throw new WebServiceException("fail invalid participant type " + id);
}
} else {
throw new WebServiceException("fail unknown participant " + id);
}
} else if (command.equals("cannotComplete")) {
bindCommands(commandList, bindings);
String id = commandList.remove(idx);
size--;
ScriptedTestParticipant participant = participantMap.get(id);
if (participant != null ) {
if (participant instanceof ParticipantCompletionTestParticipant) {
ParticipantCompletionTestParticipant baparticipant = (ParticipantCompletionTestParticipant)participant;
BAParticipantManager manager = managerMap.get(id);
try {
manager.cannotComplete();
resultsList.add("ok");
} catch (Exception e) {
throw new WebServiceException("cannotComplete " + id + " failed with exception " + e);
}
} else {
throw new WebServiceException("cannotComplete invalid participant type " + id);
}
} else {
throw new WebServiceException("cannotComplete unknown participant " + id);
}
} else if (command.equals("serve")) {
// dispatch commands to a server for execution
// we should find a web service URL and a list of commands to dispatch to that service
String url = commandList.remove(idx);
size--;
// we throw an error if the server url is unbound but we allow
// unbound variables in the command list passed on to the server
// since they may be bound by bind commands in the command list
// being served.
url = bindCommand(url, bindings, true);
bindCommands(commandList, bindings, false);
CommandsType newCommands = new CommandsType();
List<String> newCommandList = newCommands.getCommandList();
for (int i = 0; i < size; i++) {
newCommandList.add(commandList.get(i));
}
ResultsType subResults = serveSubordinate(url, newCommands);
List<String> subResultsList = subResults.getResultList();
size = subResultsList.size();
for (idx = 0; idx < size; idx++) {
resultsList.add(subResultsList.get(idx));
}
} else if (command.equals("subtransaction")) {
// create subordinate AT transaction
// this is surplus to requirements since we should really be running against a service which uses
// the subordinate interposition JaxWS handler to install a subordinate transaction before
// entering the service method. we ought to test that handler rather than hand crank the
// interposition in the service
TxContext currentTx;
TxContext newTx;
try {
currentTx = TransactionManager.getTransactionManager().currentTransaction();
} catch (SystemException e) {
throw new WebServiceException("subtransaction currentTransaction() failed with exception " + e);
}
try {
UserTransaction userTransaction = UserTransactionFactory.userSubordinateTransaction();
userTransaction.begin();
newTx = TransactionManager.getTransactionManager().currentTransaction();
} catch (Exception e) {
throw new WebServiceException("subtransaction begin() failed with exception " + e);
}
String id = transactionId("at");
subordinateTransactionMap.put(id, newTx);
resultsList.add(id);
} else if (command.equals("subactivity")) {
// create subordinate BA transaction
// this is surplus ot requirements since we should really be running against a service which uses
// the subordinate interposition JaxWS handler to install a subordinate activity before
// entering the service method. we ought to test that handler rather than hand crank the
// interposition in the service
TxContext currentTx;
TxContext newTx;
try {
currentTx = BusinessActivityManagerFactory.businessActivityManager().currentTransaction();
} catch (SystemException e) {
throw new WebServiceException("subtransaction currentTransaction() failed with exception " + e);
}
try {
UserBusinessActivity userBusinessActivity = UserBusinessActivityFactory.userBusinessActivity();
// this is nto implemented yet!!!
// userBusinessActivity.beginSubordinate();
// and this will fail with a WrongStateException
userBusinessActivity.begin();
newTx = BusinessActivityManager.getBusinessActivityManager().currentTransaction();
} catch (Exception e) {
throw new WebServiceException("subtransaction begin() failed with exception " + e);
}
String id = transactionId("ba");
subordinateActivityMap.put(id, newTx);
resultsList.add(id);
} else if (command.equals("subtransactionserve")) {
// dispatch commands in a subordinate transaction or activity
// we should find the id of a subordinate transaction, a web service URL
// and a list of commands to dispatch to that transaction
// the txid and url must be resolved if supplied as bindings
String txId = bindCommand(commandList.remove(idx), bindings, true);
size--;
String url = bindCommand(commandList.remove(idx), bindings, true);
size--;
TxContext newTx = subordinateTransactionMap.get(txId);
if (newTx != null) {
try {
TransactionManager.getTransactionManager().resume(newTx);
} catch (Exception e) {
throw new WebServiceException("subtransactioncommands resume() failed with exception " + e);
}
} else {
throw new WebServiceException("subtransactioncommands unknown subordinate transaction id " + txId);
}
// ok, now we install the relevant transaction and then just pass the commands on to
// the web service
// we allow unresolved variable references in the rest of the command list as
// they may be satisfied by embedded bind commands
bindCommands(commandList, bindings, false);
CommandsType newCommands = new CommandsType();
List<String> newCommandList = newCommands.getCommandList();
for (int i = 0; i < size; i++) {
newCommandList.add(commandList.get(i));
}
ResultsType subResults = serveSubordinate(url, newCommands);
List<String> subResultsList = subResults.getResultList();
size = subResultsList.size();
for (idx = 0; idx < size; idx++) {
resultsList.add(subResultsList.get(idx));
}
} else if (command.equals("subactivityserve")) {
// dispatch commands in a subordinate transaction or activity
// we should find the id of a subordinate transaction, a web service URL
// and a list of commands to dispatch to that transaction
// the txid and url must be resolved if supplied as bindings
String txId = bindCommand(commandList.remove(idx), bindings, true);
size--;
String url = bindCommand(commandList.remove(idx), bindings, true);
size--;
TxContext newTx = subordinateActivityMap.get(txId);
if (newTx != null) {
try {
TransactionManager.getTransactionManager().resume(newTx);
} catch (Exception e) {
throw new WebServiceException("subactivitycommands resume() failed with exception " + e);
}
} else {
throw new WebServiceException("subactivitycommands unknown subordinate transaction id " + txId);
}
// ok, now we install the relevant transaction and then just pass the commands on to
// the web service
// we allow unresolved variable references in the rest of the command list as
// they may be satisfied by embedded bind commands
bindCommands(commandList, bindings, false);
CommandsType newCommands = new CommandsType();
List<String> newCommandList = newCommands.getCommandList();
for (int i = 0; i < size; i++) {
newCommandList.add(commandList.get(i));
}
ResultsType subResults = serveSubordinate(url, newCommands);
List<String> subResultsList = subResults.getResultList();
size = subResultsList.size();
for (idx = 0; idx < size; idx++) {
resultsList.add(subResultsList.get(idx));
}
}
}
}
/**
* execute a block of commands by recursively executing each embedded command list. results are
* verified and bound to command variables in accordance with embedded bind commands. command
* variable references in embedded command lists are substituted with the corresponding bound
* values before recursive execution is performed.
*
* @param commandList
* @param resultsList
* @throws WebServiceException
*/
private void processCommandBlock(List<String> commandList, List<String> resultsList, HashMap<String, String> bindings)
throws WebServiceException
{
// break up the command block into a list of nested command lists. successive command lists will
// be separated by a "next" or "bind" command. the block will be terminated by an "endblock" command.
// nested commands may themselves be block commands.
List<List<String>> subcommandsList = new ArrayList<List<String>>();
List<String> subcommands = new ArrayList<String>();
// each subcommand list needs to start with next or bind
subcommands.add("next");
int size = commandList.size();
int depth = 0;
int idx;
for (idx = 0; idx < size; idx++)
{
String command = commandList.get(idx);
if (depth > 0) {
// track nesting levels
if (command.equals("block")) {
depth++;
} else if (command.equals("endblock")) {
depth--;
}
subcommands.add(command);
} else {
if (command.equals("block")) {
// add nested block command to sublist
subcommands.add(command);
depth++;
} else if (command.equals("next")) {
// create new sublist starting with next
subcommandsList.add(subcommands);
subcommands = new ArrayList<String>();
subcommands.add(command);
} else if (command.equals("bindings")) {
// create new sublist starting with bind
subcommandsList.add(subcommands);
subcommands = new ArrayList<String>();
subcommands.add(command);
} else if (command.equals("endblock")) {
// should be at end of commandlist
if (idx != size - 1) {
throw new WebServiceException("block commands reached endblock before end of block list");
}
subcommandsList.add(subcommands);
} else {
// just append command to current subcommands list
subcommands.add(command);
}
}
}
// ok, we should have a list of command lists all starting with either next or bind
// now we execute each next command list in turn after substituting bound variables
// of the form $xxx and we execute each bind command list by testing result values
// against literals or binding result values to variables and possibly outputting
// results to the global results list
List<String> subresultsList = null;
size = subcommandsList.size();
for (idx = 0; idx < size; idx++) {
subcommands = subcommandsList.get(idx);
String command = subcommands.remove(0);
if (command.equals("next")) {
subresultsList = new ArrayList<String>();
bindCommands(subcommands, bindings);
processCommands(subcommands, subresultsList);
} else if (command.equals("bindings")) {
bindResults(subcommands, subresultsList, resultsList, bindings);
}
}
resultsList.addAll(subresultsList);
}
/**
* for each command in the command list which contains variable references substitute a command containing
* the value for the variable found in bindings. variables are mentioned by wrappng their name in braces.
*
* @param commands the list of commands to be processed
* @param bindings a map from variable names to the associated values
* @throws WebServiceException if a variable reference is invalidly formatted or refers
* to an unbound variable
*/
private void bindCommands(List<String> commands, HashMap<String, String> bindings)
throws WebServiceException
{
bindCommands(commands, bindings, true);
}
/**
* for each command in the command list which contains variable references substitute a command containing
* the value for the variable found in bindings. variables are mentioned by wrappng their name in braces.
*
* @param commands the list of commands to be processed
* @param bindings a map from variable names to the associated values
* @param mustResolve is true if references to unbound variables should result in a thrown exception and false
* if they should be tolerated by being left unsubstituted
* @throws WebServiceException if a variable reference is invalidly formatted or refers
* to an unbound variable when mustResolve is true
*/
private void bindCommands(List<String> commands, HashMap<String, String> bindings, boolean mustResolve)
throws WebServiceException
{
int size = commands.size();
int idx;
for (idx = 0; idx < size; idx++) {
// pop the command and append either the original or a substituted copy
String command = commands.remove(0);
String newCommand = bindCommand(command, bindings, mustResolve);
commands.add(newCommand.toString());
}
}
/**
* substitute any bound variables found in the supplied command
* @param command the command to be substituted
* @param bindings the map from currently bound variables to their string values
* @param mustResolve is true if references to unbound variables should result in a thrown exception and false
* if they should be tolerated by being left unsubstituted
* @return the substituted command or the original command if it contains no variable references
* @throws WebServiceException if a variable reference is invalidly formatted or refers
* to an unbound variable
*/
private String bindCommand(String command, HashMap<String, String> bindings, boolean mustResolve)
throws WebServiceException
{
if (command.contains("{")) {
StringBuffer newCommandBuffer = new StringBuffer();
int len = command.length();
int pos = 0;
while (pos < len) {
char c = command.charAt(pos);
if (c == '{') {
// must have room for at least one character and a closing brace
if (pos > len-2) {
throw new WebServiceException("bindCommand : invalid variable reference " + command + " @ " + pos);
}
// brace cannot be next character so start search at pos+2
int endpos = command.indexOf('}', pos + 2);
if (endpos < 0) {
throw new WebServiceException("bindCommand : invalid variable reference " + command + " @ " + pos);
}
String var = command.substring(pos + 1, endpos);
// var must be alphanumeric
if (!var.matches("[0-9a-zA-Z]+")) {
throw new WebServiceException("bindCommand : invalid variable name " + command + " @ " + pos);
}
String val = bindings.get(var);
if (val == null) {
if (mustResolve) {
throw new WebServiceException("bindCommand : unbound variable " + command + " @ " + pos);
} else {
newCommandBuffer.append('{');
newCommandBuffer.append(var);
newCommandBuffer.append('}');
}
} else {
newCommandBuffer.append(val);
}
pos = endpos+1;
} else {
newCommandBuffer.append(c);
}
}
return newCommandBuffer.toString();
} else {
return command;
}
}
/**
* process each command in the binding commands list. binding commands include "set var idx"
* which binds a variable to a value obtained from inResults, "check idx value" which checks that a
* value obtaind from inResults has a given value and "output idx", which inserts a value obtained
* from inResults into outResults.
* @param commands
* @param inResults
* @param outResults
* @param bindings
* @throws WebServiceException
*/
private void bindResults(List<String> commands, List<String> inResults, List<String> outResults, HashMap<String, String> bindings)
throws WebServiceException
{
int size = commands.size();
int idx;
for (idx = 0; idx < size; idx++) {
String command = commands.get(idx).trim();
if (command.equals("bind")) {
// bind var idx : var <== inResults[idx]
if (idx + 2 >= size) {
throw new WebServiceException("bindResults : invalid set format " + command);
}
String var = commands.get(idx + 1).trim();
String resultIdxString = commands.get(idx + 2).trim();
idx += 2;
if (!var.matches("[0-9a-zA-Z]+") || !resultIdxString.matches("[0-9]")) {
throw new WebServiceException("bindResults : invalid set format " + command);
}
Integer resultIdx;
try {
resultIdx = Integer.valueOf(resultIdxString);
} catch (NumberFormatException nfe) {
throw new WebServiceException("bindResults : invalid set index " + command);
}
if (resultIdx < 0 || resultIdx >= inResults.size()) {
throw new WebServiceException("bindResults : invalid set index " + command);
}
bindings.put(var, inResults.get(resultIdx));
} else if (command.equals("check")) {
// check value idx : ensure inResults[idx] == value
if (idx + 2 >= size) {
throw new WebServiceException("bindResults : invalid check format " + command);
}
String val = commands.get(idx + 1).trim();
String resultIdxString = commands.get(idx + 2).trim();
if (!resultIdxString.matches("[0-9]")) {
throw new WebServiceException("bindResults : invalid set format " + command);
}
idx += 2;
Integer resultIdx;
try {
resultIdx = Integer.valueOf(resultIdxString);
} catch (NumberFormatException nfe) {
throw new WebServiceException("bindResults : invalid check index " + command);
}
if (resultIdx < 0 || resultIdx >= inResults.size()) {
throw new WebServiceException("bindResults : invalid check index " + command);
}
String result = inResults.get(idx);
if (!result.equals(val)) {
throw new WebServiceException("bindResults : check failed, expecting " + val + " got " + result);
}
} else if (command.equals("output")) {
// output idx : outResults add inResults[idx];
if (idx + 1 >= size) {
throw new WebServiceException("bindResults : invalid output format " + command);
}
String resultIdxString = commands.get(idx + 1).trim();
idx += 1;
if (!resultIdxString.matches("[0-9]")) {
throw new WebServiceException("bindResults : invalid set format " + command);
}
Integer resultIdx;
try {
resultIdx = Integer.valueOf(resultIdxString);
} catch (NumberFormatException nfe) {
throw new WebServiceException("bindResults : invalid output index " + command);
}
if (resultIdx < 0 || resultIdx >= inResults.size()) {
throw new WebServiceException("bindResults : invalid output index " + command);
}
String result = inResults.get(idx);
outResults.add(result);
} else {
throw new WebServiceException("bindResults : invalid bind commmand " + command);
}
}
}
/**
* utiilty method provided to simplify recursive dispatch of commands to another web service. this is
* intended to be used to create and drive participants in subordinate transactions
* @param url
* @param commands
* @return
*/
private ResultsType serveSubordinate(String url, CommandsType commands)
{
return getClient().serve(url, commands);
}
private synchronized XTSServiceTestClient getClient()
{
if (client == null) {
client = new XTSServiceTestClient();
}
return client;
}
/**
* counter used to conjure up participant names
*/
private static int nextId = 0;
/**
* obtain a new participant name starting with a prefix recognised by the recovery code and terminated
* with the supplied suffix and a unique trailing number
* @param suffix a component to be added to the name before the counter identifying the type of
* participant
* @return
*/
private synchronized String participantId(String suffix)
{
return Constants.PARTICIPANT_ID_PREFIX + suffix + "." + nextId++;
}
/**
* obtain a new transaction name starting with a transaction prefix and terminated
* with the supplied suffix and a unique trailing number
* @param suffix a component to be added to the name before the counter identifying the type of
* transaction
* @return
*/
private synchronized String transactionId(String suffix)
{
return Constants.TRANSACTION_ID_PREFIX + suffix + "." + nextId++;
}
/**
* map used to identify specific service implementations with server url paths
*/
static private HashMap<String, XTSServiceTestInterpreter> serviceMap = new HashMap<String, XTSServiceTestInterpreter>();
/**
* a table used to retain a handle on enlisted participants so that they can be driven by the client to
* perform actions not contained in the original command script.
*/
private HashMap<String, ScriptedTestParticipant> participantMap;
/**
* a table used to retain a handle on managers for enlisted BA participants.
*/
private HashMap<String, BAParticipantManager> managerMap;
/**
* a table used to retain a handle on AT subordinate transactions
*/
private HashMap<String, TxContext> subordinateTransactionMap;
/**
* a table used to retain a handle on BA subactivities
*/
private HashMap<String, TxContext> subordinateActivityMap;
/**
* a table of default command variable bindings
*/
private HashMap<String, String> defaultBindings;
/**
* a client used to propagate requests recursively from within subtransactions or subactivities
*/
private XTSServiceTestClient client;
}