net.bpelunit.framework.SpecificationLoader.java Source code

Java tutorial

Introduction

Here is the source code for net.bpelunit.framework.SpecificationLoader.java

Source

/**
 * This file belongs to the BPELUnit utility and Eclipse plugin set. See enclosed
 * license file for more information.
 * 
 */
package net.bpelunit.framework;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.wsdl.Definition;
import javax.wsdl.WSDLException;
import javax.wsdl.factory.WSDLFactory;
import javax.wsdl.xml.WSDLReader;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import net.bpelunit.framework.control.datasource.DataSourceUtil;
import net.bpelunit.framework.control.deploy.IBPELDeployer;
import net.bpelunit.framework.control.ext.IDataSource;
import net.bpelunit.framework.control.ext.IHeaderProcessor;
import net.bpelunit.framework.control.ext.ISOAPEncoder;
import net.bpelunit.framework.control.soap.NamespaceContextImpl;
import net.bpelunit.framework.control.util.ActivityUtil;
import net.bpelunit.framework.control.util.ActivityUtil.ActivityConstant;
import net.bpelunit.framework.control.util.BPELUnitConstants;
import net.bpelunit.framework.control.util.BPELUnitUtil;
import net.bpelunit.framework.exception.DataSourceException;
import net.bpelunit.framework.exception.SpecificationException;
import net.bpelunit.framework.model.HumanPartner;
import net.bpelunit.framework.model.Partner;
import net.bpelunit.framework.model.ProcessUnderTest;
import net.bpelunit.framework.model.test.PartnerTrack;
import net.bpelunit.framework.model.test.TestCase;
import net.bpelunit.framework.model.test.TestSuite;
import net.bpelunit.framework.model.test.activity.Activity;
import net.bpelunit.framework.model.test.activity.CompleteHumanTask;
import net.bpelunit.framework.model.test.activity.ReceiveAsync;
import net.bpelunit.framework.model.test.activity.ReceiveSendAsync;
import net.bpelunit.framework.model.test.activity.ReceiveSendSync;
import net.bpelunit.framework.model.test.activity.SendAsync;
import net.bpelunit.framework.model.test.activity.SendReceiveAsync;
import net.bpelunit.framework.model.test.activity.SendReceiveSync;
import net.bpelunit.framework.model.test.activity.TwoWayAsyncActivity;
import net.bpelunit.framework.model.test.activity.Wait;
import net.bpelunit.framework.model.test.data.CompleteHumanTaskSpecification;
import net.bpelunit.framework.model.test.data.DataCopyOperation;
import net.bpelunit.framework.model.test.data.DataSpecification;
import net.bpelunit.framework.model.test.data.ReceiveCondition;
import net.bpelunit.framework.model.test.data.ReceiveDataSpecification;
import net.bpelunit.framework.model.test.data.SOAPOperationCallIdentifier;
import net.bpelunit.framework.model.test.data.SOAPOperationDirectionIdentifier;
import net.bpelunit.framework.model.test.data.SendDataSpecification;
import net.bpelunit.framework.model.test.data.extraction.DataExtraction;
import net.bpelunit.framework.verify.ConditionGroupsExistInTestSuiteValidator;
import net.bpelunit.framework.verify.ITestSuiteValidator;
import net.bpelunit.framework.verify.NoCyclesInConditionGroupInheritanceValidator;
import net.bpelunit.framework.verify.PartnersHaveUniqueNamesValidator;
import net.bpelunit.framework.verify.PartnersInTestCasesHaveNamesValidator;
import net.bpelunit.framework.verify.PartnersUsedInTestCaseAreDeclaredInTestSuiteValidator;
import net.bpelunit.framework.verify.TestCaseNamesAreUniqueValidator;
import net.bpelunit.framework.verify.TestSuiteRootInformationValidator;
import net.bpelunit.framework.verify.TestSuiteXMLValidator;
import net.bpelunit.framework.verify.XMLDataIsEitherSetOrImportedValidator;
import net.bpelunit.framework.xml.suite.XMLActivity;
import net.bpelunit.framework.xml.suite.XMLAnyElement;
import net.bpelunit.framework.xml.suite.XMLCompleteHumanTaskActivity;
import net.bpelunit.framework.xml.suite.XMLCondition;
import net.bpelunit.framework.xml.suite.XMLConditionGroup;
import net.bpelunit.framework.xml.suite.XMLCopy;
import net.bpelunit.framework.xml.suite.XMLDataExtraction;
import net.bpelunit.framework.xml.suite.XMLDeploymentSection;
import net.bpelunit.framework.xml.suite.XMLHeaderProcessor;
import net.bpelunit.framework.xml.suite.XMLHumanPartnerDeploymentInformation;
import net.bpelunit.framework.xml.suite.XMLHumanPartnerTrack;
import net.bpelunit.framework.xml.suite.XMLMapping;
import net.bpelunit.framework.xml.suite.XMLPUTDeploymentInformation;
import net.bpelunit.framework.xml.suite.XMLPartnerDeploymentInformation;
import net.bpelunit.framework.xml.suite.XMLPartnerTrack;
import net.bpelunit.framework.xml.suite.XMLProperty;
import net.bpelunit.framework.xml.suite.XMLReceiveActivity;
import net.bpelunit.framework.xml.suite.XMLSendActivity;
import net.bpelunit.framework.xml.suite.XMLSendOnlyActivity;
import net.bpelunit.framework.xml.suite.XMLSetUp;
import net.bpelunit.framework.xml.suite.XMLSoapActivity;
import net.bpelunit.framework.xml.suite.XMLTestCase;
import net.bpelunit.framework.xml.suite.XMLTestCasesSection;
import net.bpelunit.framework.xml.suite.XMLTestSuite;
import net.bpelunit.framework.xml.suite.XMLTestSuiteDocument;
import net.bpelunit.framework.xml.suite.XMLTrack;
import net.bpelunit.framework.xml.suite.XMLTwoWayActivity;
import net.bpelunit.framework.xml.suite.XMLWaitActivity;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.ibm.wsdl.Constants;

/**
 * The specificaton loader reads test suite documents and creates the in-memory
 * test run data structure, which is made ready for test execution.
 * 
 * @version $Id$
 * @author Philip Mayer
 * 
 */
public class SpecificationLoader {

    private Logger fLogger;
    private BPELUnitRunner fRunner;

    private Map<String, XMLConditionGroup> conditionGroups = new HashMap<String, XMLConditionGroup>();

    public SpecificationLoader(BPELUnitRunner runner) {
        fRunner = runner;
        fLogger = Logger.getLogger(getClass());
    }

    public TestSuite loadTestSuite(File suite) throws SpecificationException {
        try {
            fLogger.info("Loading test suite from file " + suite);

            String path = suite.getCanonicalFile().getParent() + File.separator;
            fLogger.info("Using base path: " + path);

            XMLTestSuiteDocument doc = XMLTestSuiteDocument.Factory.parse(suite);
            validateTestSuite(doc);
            extractConditionGroups(doc);
            TestSuite testSuite = parseSuite(path, doc);

            fLogger.info("Loaded test suite with name \"" + testSuite.getName() + "\" and "
                    + testSuite.getTestCaseCount() + " test cases.");

            return testSuite;

        } catch (XmlException e) {
            throw new SpecificationException("An XML reading error occurred when reading the test suite file.", e);
        } catch (IOException e) {
            throw new SpecificationException("An I/O error occurred when reading the test suite file.", e);
        }
    }

    private void extractConditionGroups(XMLTestSuiteDocument doc) {
        if (doc == null || doc.getTestSuite() == null || doc.getTestSuite().getConditionGroups() == null) {
            return;
        }

        List<XMLConditionGroup> cgList = doc.getTestSuite().getConditionGroups().getConditionGroupList();
        if (cgList != null) {
            for (XMLConditionGroup cg : cgList) {
                conditionGroups.put(cg.getName(), cg);
            }
        }
    }

    private void validateTestSuite(final XMLTestSuiteDocument doc) throws SpecificationException {
        ITestSuiteValidator[] validators = new ITestSuiteValidator[] { new TestSuiteXMLValidator(),
                new PartnersHaveUniqueNamesValidator(), new PartnersUsedInTestCaseAreDeclaredInTestSuiteValidator(),
                new TestSuiteRootInformationValidator(), new PartnersInTestCasesHaveNamesValidator(),
                new ConditionGroupsExistInTestSuiteValidator(), new NoCyclesInConditionGroupInheritanceValidator(),
                new XMLDataIsEitherSetOrImportedValidator(), new TestCaseNamesAreUniqueValidator() };

        for (ITestSuiteValidator v : validators) {
            v.validate(doc);
        }
    }

    private TestSuite parseSuite(String testDirectory, XMLTestSuiteDocument xmlTestSuiteDocument)
            throws SpecificationException, XmlException, IOException {

        XMLTestSuite xmlTestSuite = xmlTestSuiteDocument.getTestSuite();

        // Name
        String xmlSuiteName = xmlTestSuite.getName();

        URL suiteBaseURL = getBaseURL(xmlTestSuite);

        // Load deployment information
        XMLDeploymentSection xmlDeployment = xmlTestSuite.getDeployment();

        /*
         * The Process Under Test and his Deployer.
         */

        XMLPUTDeploymentInformation xmlPut = xmlDeployment.getPut();

        Map<String, Partner> suitePartners = new HashMap<String, Partner>();
        ProcessUnderTest processUnderTest = createProcessUnderTest(testDirectory, suiteBaseURL, suitePartners,
                xmlPut);

        /*
         * The Client. Note that the client uses the PUT's WSDL. This is
         * intended as the clients activities will all deal with the partners
         * operations.
         */

        String xmlPutWSDL = xmlPut.getWsdl();
        String xmlPutPartnerWSDL = xmlPut.getPartnerWSDL();

        Partner suiteClient = new Partner(BPELUnitConstants.CLIENT_NAME,
                loadWsdlDefinition(testDirectory, xmlPutWSDL, BPELUnitConstants.CLIENT_NAME),
                loadWsdlDefinition(testDirectory, xmlPutPartnerWSDL, BPELUnitConstants.CLIENT_NAME),
                suiteBaseURL.toString());

        /*
         * The Partners. Each partner is initialized with the attached WSDL
         * information, which allows retrieving operations from this partner
         * later on.
         */

        createPartners(testDirectory, suiteBaseURL, xmlDeployment, suitePartners);
        Map<String, HumanPartner> humanPartners = createHumanPartners(xmlDeployment, suiteBaseURL);

        // Create the suite.
        TestSuite suite = new TestSuite(xmlSuiteName, suiteBaseURL, processUnderTest);

        // Process the contents of the setUp block, if any
        try {
            readTestSuiteSetUpBlock(suite, xmlTestSuite);
        } catch (Exception ex) {
            throw new SpecificationException("Error during test suite set up", ex);
        }

        /*
         * The Test Cases. Each test case consists of a number of Partner
         * Tracks, which in turn consist of an ordered collection of Activities.
         */

        createTestCases(testDirectory, xmlTestSuiteDocument, xmlTestSuite, suitePartners, humanPartners,
                suiteClient, suite);

        return suite;
    }

    private Map<String, HumanPartner> createHumanPartners(XMLDeploymentSection xmlDeployment, URL baseURL)
            throws SpecificationException {
        Map<String, HumanPartner> humanPartners = new HashMap<String, HumanPartner>();

        if (xmlDeployment.getHumanPartnerList() != null) {
            for (XMLHumanPartnerDeploymentInformation hp : xmlDeployment.getHumanPartnerList()) {
                humanPartners.put(hp.getName(), new HumanPartner(hp.getName(), baseURL, hp.getWshtEndpoint(),
                        hp.getUsername(), hp.getPassword()));
            }
        }

        return humanPartners;
    }

    private void createTestCases(String testDirectory, XMLTestSuiteDocument xmlTestSuiteDocument,
            XMLTestSuite xmlTestSuite, Map<String, Partner> suitePartners,
            Map<String, HumanPartner> suiteHumanPartners, Partner suiteClient, TestSuite suite)
            throws SpecificationException {
        XMLTestCasesSection xmlTestCases = xmlTestSuite.getTestCases();
        List<XMLTestCase> xmlTestCaseList = xmlTestCases.getTestCaseList();

        int currentNumber = 0;
        for (XMLTestCase xmlTestCase : xmlTestCaseList) {
            currentNumber++;
            createTestCase(testDirectory, xmlTestSuiteDocument, xmlTestSuite, suitePartners, suiteHumanPartners,
                    suiteClient, suite, currentNumber, xmlTestCase);
        }
    }

    private void createTestCase(String testDirectory, XMLTestSuiteDocument xmlTestSuiteDocument,
            XMLTestSuite xmlTestSuite, Map<String, Partner> suitePartners,
            Map<String, HumanPartner> suiteHumanPartners, Partner suiteClient, TestSuite suite, int currentNumber,
            XMLTestCase xmlTestCase) throws SpecificationException {
        String xmlTestCaseName = getTestCaseName(currentNumber, xmlTestCase);

        boolean isVary = xmlTestCase.getVary();
        int rounds = computeNumberOfRounds(xmlTestSuiteDocument, isVary);
        fLogger.info("Varying: " + isVary + " (Rounds: " + rounds + ")");

        IDataSource dataSource = readDataSource(testDirectory, xmlTestSuite, xmlTestCase);

        final int nRows = getNumberOfRows(dataSource);
        final int nRounds = getNumberOfRounds(isVary, rounds);
        for (int iRow = 0; iRow < nRows; ++iRow) {
            for (int iRound = 0; iRound < nRounds; iRound++) {
                String currentTestCaseName = getHumanReadableTestCaseName(xmlTestCaseName, rounds, dataSource, iRow,
                        iRound);
                if (!xmlTestCase.getAbstract()) {
                    TestCase test = createTestCase(suitePartners, suiteHumanPartners, suiteClient, suite,
                            xmlTestCase, currentTestCaseName, iRound, testDirectory);
                    test.setDataSource(dataSource);
                    test.setRowIndex(iRow);
                    suite.addTestCase(test);
                }
            }
        }
    }

    private String getHumanReadableTestCaseName(String xmlTestCaseName, int rounds, IDataSource dataSource,
            int iRow, int iRound) {
        String currentTestCaseName = xmlTestCaseName;
        if (dataSource != null) {
            currentTestCaseName += " (Row " + (iRow + 1) + ")";
        }
        if (rounds > 1) {
            // Create a non-computer-science name ;)
            currentTestCaseName += " (Round " + (iRound + 1) + ")";
        }
        return currentTestCaseName;
    }

    private int getNumberOfRounds(boolean isVary, int rounds) {
        return isVary && rounds > 0 ? rounds : 1;
    }

    private int getNumberOfRows(IDataSource dataSource) {
        final int nRows = dataSource != null ? dataSource.getNumberOfRows() : 1;
        return nRows;
    }

    private String getTestCaseName(int currentNumber, XMLTestCase xmlTestCase) {
        String xmlTestCaseName = xmlTestCase.getName();
        if (xmlTestCaseName == null) {
            xmlTestCaseName = "Test Case " + currentNumber;
        }
        return xmlTestCaseName;
    }

    private IDataSource readDataSource(String testDirectory, XMLTestSuite xmlTestSuite, XMLTestCase xmlTestCase)
            throws SpecificationException {
        IDataSource dataSource;
        try {
            dataSource = DataSourceUtil.createDataSource(xmlTestSuite, xmlTestCase, new File(testDirectory),
                    fRunner);
        } catch (DataSourceException e) {
            throw new SpecificationException(
                    "There was a problem while " + "initializing the specified data source.", e);
        }
        return dataSource;
    }

    private void createPartners(String testDirectory, URL suiteBaseURL, XMLDeploymentSection xmlDeployment,
            Map<String, Partner> suitePartners) throws SpecificationException {
        for (XMLPartnerDeploymentInformation xmlPDI : xmlDeployment.getPartnerList()) {
            String name = xmlPDI.getName();
            Partner p = new Partner(name, loadWsdlDefinition(testDirectory, xmlPDI.getWsdl(), name),
                    loadWsdlDefinition(testDirectory, xmlPDI.getPartnerWsdl(), name), suiteBaseURL.toString());
            suitePartners.put(p.getName(), p);
        }
    }

    public static Definition loadWsdlDefinition(String baseDir, String wsdlFileName, String partnerName)
            throws SpecificationException {
        if (wsdlFileName == null || wsdlFileName.equals("")) {
            return null;
        }

        return loadWsdlDefinition(new File(baseDir, wsdlFileName).getPath(), partnerName);
    }

    /**
     * TODO FIXME Used in tests as well but this is not clean...
     * 
     * @param wsdlFileName
     * @param partnerName
     * @return
     * @throws SpecificationException
     */
    public static Definition loadWsdlDefinition(String wsdlFileName, String partnerName)
            throws SpecificationException {

        // Check file exists
        if (!new File(wsdlFileName).exists()) {
            throw new SpecificationException("Cannot read WSDL file for partner " + partnerName + ": File \""
                    + wsdlFileName + "\" not found.");
        }

        // load WSDL
        try {
            WSDLFactory factory = WSDLFactory.newInstance();
            WSDLReader reader = factory.newWSDLReader();
            reader.setFeature(Constants.FEATURE_VERBOSE, false);
            return reader.readWSDL(wsdlFileName);
        } catch (WSDLException e) {
            throw new SpecificationException(
                    "Error while reading WSDL for partner " + partnerName + " from file \"" + wsdlFileName + "\".",
                    e);
        }
    }

    private ProcessUnderTest createProcessUnderTest(String testDirectory, URL suiteBaseURL,
            Map<String, Partner> suitePartners, XMLPUTDeploymentInformation xmlPut) throws SpecificationException {
        String xmlPutName = xmlPut.getName();
        String xmlPutWSDL = xmlPut.getWsdl();
        String xmlPutPartnerWSDL = xmlPut.getPartnerWSDL();
        String xmlPutType = xmlPut.getType();

        ProcessUnderTest processUnderTest = new ProcessUnderTest(xmlPutName, testDirectory,
                loadWsdlDefinition(testDirectory, xmlPutWSDL, xmlPutName),
                loadWsdlDefinition(testDirectory, xmlPutPartnerWSDL, xmlPutName), suiteBaseURL.toString());

        for (XMLProperty property : xmlPut.getPropertyList()) {
            processUnderTest.setXMLDeploymentOption(property.getName(), property.getStringValue());
        }

        IBPELDeployer suitePutDeployer = fRunner.createNewDeployer(xmlPutType);
        processUnderTest.setDeployer(suitePutDeployer);
        processUnderTest.setGlobalConfiguration(fRunner.getGlobalConfigurationForDeployer(suitePutDeployer));

        // Add the put to the partners in case of running in test mode
        suitePartners.put(processUnderTest.getName(), processUnderTest);

        /*
         * Add partners of the process to put in order to facilitate end point
         * replacement
         */
        processUnderTest.setPartners(suitePartners);
        return processUnderTest;
    }

    private URL getBaseURL(XMLTestSuite xmlTestSuite) throws SpecificationException {
        try {
            // Use the local base URL, and if that doesn't work, try the default
            // one
            String xmlUrl = xmlTestSuite.getBaseURL();
            if (xmlUrl == null) {
                xmlUrl = BPELUnitConstants.DEFAULT_BASE_URL;
            }
            URL suiteBaseURL = new URL(xmlUrl);

            // Normalize the URL: add port and trailing slash if missing
            int port = suiteBaseURL.getPort();
            if (port == -1) {
                port = BPELUnitConstants.DEFAULT_BASE_PORT;
            }
            String path = suiteBaseURL.getPath();
            if (!path.endsWith("/")) {
                path += "/";
            }
            suiteBaseURL = new URL(suiteBaseURL.getProtocol(), suiteBaseURL.getHost(), port, path);

            return suiteBaseURL;
        } catch (MalformedURLException e) {
            throw new SpecificationException("Could not create a valid URL from specified base URL.", e);
        }
    }

    private int computeNumberOfRounds(XMLTestSuiteDocument xmlTestSuiteDocument, boolean isVary)
            throws SpecificationException {
        int rounds;
        rounds = 0;

        if (isVary) {
            /*
             * This is a varying test case. Each activity may have an arbitrary
             * number of delay times specified. These lists SHOULD be of equal
             * length, although no varying also also okay.
             * 
             * We use XPath to find all delay sequences and find the highest
             * size. This will be the size expected from all activities.
             */
            XPath xpath = XPathFactory.newInstance().newXPath();
            NamespaceContextImpl nsContext = new NamespaceContextImpl();
            nsContext.setNamespace("ts", BPELUnitConstants.BPELUNIT_TESTSUITE_NAMESPACE);
            xpath.setNamespaceContext(nsContext);
            try {
                NodeList set = (NodeList) xpath.evaluate("//@delaySequence", xmlTestSuiteDocument.getDomNode(),
                        XPathConstants.NODESET);
                int currentMax = 0;
                for (int i = 0; i < set.getLength(); i++) {
                    if (set.item(i) instanceof Attr) {
                        Attr attr = (Attr) set.item(i);
                        List<Double> ints = getRoundInformation(attr.getValue());
                        if (ints != null) {
                            currentMax = ints.size();
                        }
                    }
                }
                rounds = currentMax;
            } catch (XPathExpressionException e) {
                // This should not happen.
                throw new SpecificationException(
                        "There was a problem finding delay sequences. This most likely indicates a bug in the framework.",
                        e);
            }
        }
        return rounds;
    }

    private void readTestSuiteSetUpBlock(TestSuite testSuite, XMLTestSuite xmlTestSuite) {
        if (!xmlTestSuite.isSetSetUp()) {
            return;
        }

        XMLSetUp xmlSetUp = xmlTestSuite.getSetUp();
        if (xmlSetUp.isSetScript()) {
            testSuite.setSetUpVelocityScript(xmlSetUp.getScript());
        }
    }

    private TestCase createTestCase(Map<String, Partner> suitePartners,
            Map<String, HumanPartner> suiteHumanPartners, Partner suiteClient, TestSuite suite,
            XMLTestCase xmlTestCase, String xmlTestCaseName, int round, String testDirectory)
            throws SpecificationException {

        TestCase test = new TestCase(suite, xmlTestCaseName);

        readMetaData(xmlTestCase, test);
        readTestCaseSetUpBlock(test, xmlTestCase);
        readClientTrack(suiteClient, xmlTestCase, round, testDirectory, test);

        // Partners Partner Track
        readPartners(suitePartners, xmlTestCase, round, testDirectory, test);
        readHumanPartners(suiteHumanPartners, xmlTestCase, testDirectory, test);
        return test;
    }

    private void readHumanPartners(Map<String, HumanPartner> suiteHumanPartners, XMLTestCase xmlTestCase,
            String testDirectory, TestCase test) throws SpecificationException {
        List<XMLHumanPartnerTrack> humanPartnerTrackList = xmlTestCase.getHumanPartnerTrackList();

        if (humanPartnerTrackList != null) {
            for (XMLHumanPartnerTrack xmlHumanPartnerTrack : humanPartnerTrackList) {
                readHumanPartner(suiteHumanPartners, testDirectory, test, xmlHumanPartnerTrack);
            }
        }

    }

    private void readHumanPartner(Map<String, HumanPartner> suiteHumanPartners, String testDirectory, TestCase test,
            XMLHumanPartnerTrack xmlHumanPartnerTrack) throws SpecificationException {
        String xmlPartnerTrackName = xmlHumanPartnerTrack.getName();
        HumanPartner realPartner = suiteHumanPartners.get(xmlPartnerTrackName);

        PartnerTrack pTrack = new PartnerTrack(test, realPartner);
        readActivities(pTrack, xmlHumanPartnerTrack, testDirectory);
        pTrack.setNamespaceContext(getNamespaceMap(xmlHumanPartnerTrack.newCursor()));
        test.addPartnerTrack(pTrack);
    }

    private void readActivities(PartnerTrack pTrack, XMLHumanPartnerTrack xmlHumanPartnerTrack,
            String testDirectory) throws SpecificationException {
        List<XMLActivity> activities = ActivityUtil.getActivities(xmlHumanPartnerTrack);

        for (XMLActivity xmlActivity : activities) {
            if (xmlActivity instanceof XMLCompleteHumanTaskActivity) {
                CompleteHumanTask activity = createCompleteHumanTaskActivity(pTrack, testDirectory,
                        (XMLCompleteHumanTaskActivity) xmlActivity);
                pTrack.addActivity(activity);
            } else {
                throw new SpecificationException("Unknown Activity in Human Partner Track " + pTrack.getName()
                        + ": " + xmlActivity.getDomNode().getLocalName());
            }
        }
    }

    private CompleteHumanTask createCompleteHumanTaskActivity(PartnerTrack pTrack, String testDirectory,
            XMLCompleteHumanTaskActivity xmlActivity) throws SpecificationException {
        CompleteHumanTask activity = new CompleteHumanTask(pTrack);
        activity.setTaskName(xmlActivity.getTaskName());
        NamespaceContext context = getNamespaceMap(xmlActivity.newCursor());

        Element rawDataRoot = null;
        String templateText = null;
        try {
            if (xmlActivity.isSetData()) {
                rawDataRoot = getLiteralDataForSend(xmlActivity.getData(), testDirectory);
            }
            if (xmlActivity.isSetTemplate()) {
                templateText = getTemplateText(testDirectory, xmlActivity.getTemplate());
            }
        } catch (Exception ex) {
            throw new SpecificationException(
                    "There was a problem while interpreting the 'src' attribute in activity " + activity, ex);
        }
        final Element literalSendDataChild = rawDataRoot != null ? (Element) rawDataRoot.getFirstChild() : null;

        CompleteHumanTaskSpecification spec = new CompleteHumanTaskSpecification(activity, context,
                literalSendDataChild, templateText, pTrack);

        // get conditions
        List<XMLCondition> xmlConditionList = xmlActivity.getConditionList();
        List<ReceiveCondition> cList = new ArrayList<ReceiveCondition>();
        if (xmlConditionList != null) {
            for (XMLCondition xmlCondition : xmlConditionList) {
                cList.add(new ReceiveCondition(spec, xmlCondition.getExpression(), xmlCondition.getTemplate(),
                        xmlCondition.getValue()));
            }
        }
        addConditionsFromConditionGroups(xmlActivity.getConditionGroupList(), spec, cList);
        spec.setConditions(cList);

        // get data extraction requests
        final List<XMLDataExtraction> xmlDataExtractionList = xmlActivity.getDataExtractionList();
        final List<DataExtraction> deList = readDataExtractionElements(spec, xmlDataExtractionList);
        spec.setDataExtractions(deList);

        activity.initialize(spec);
        return activity;
    }

    private void readPartners(Map<String, Partner> suitePartners, XMLTestCase xmlTestCase, int round,
            String testDirectory, TestCase test) throws SpecificationException {
        List<XMLPartnerTrack> partnerTrackList = xmlTestCase.getPartnerTrackList();
        if (partnerTrackList != null) {
            for (XMLPartnerTrack xmlPartnerTrack : partnerTrackList) {
                readPartner(suitePartners, xmlTestCase, round, testDirectory, test, xmlPartnerTrack);
            }
        }
    }

    private void readPartner(Map<String, Partner> suitePartners, XMLTestCase xmlTestCase, int round,
            String testDirectory, TestCase test, XMLPartnerTrack xmlPartnerTrack) throws SpecificationException {
        String xmlPartnerTrackName = xmlPartnerTrack.getName();
        Partner realPartner = suitePartners.get(xmlPartnerTrackName);

        PartnerTrack pTrack = new PartnerTrack(test, realPartner);
        readActivities(pTrack, xmlTestCase, xmlPartnerTrack, round, testDirectory);
        pTrack.setNamespaceContext(getNamespaceMap(xmlPartnerTrack.newCursor()));
        if (xmlPartnerTrack.isSetAssume()) {
            pTrack.setAssumption(xmlPartnerTrack.getAssume());
        }
        test.addPartnerTrack(pTrack);
    }

    private void readClientTrack(Partner suiteClient, XMLTestCase xmlTestCase, int round, String testDirectory,
            TestCase test) throws SpecificationException {
        XMLTrack xmlClientTrack = xmlTestCase.getClientTrack();

        PartnerTrack track = new PartnerTrack(test, suiteClient);
        readActivities(track, xmlTestCase, xmlClientTrack, round, testDirectory);
        track.setNamespaceContext(getNamespaceMap(xmlClientTrack.newCursor()));
        test.addPartnerTrack(track);
    }

    private void readMetaData(XMLTestCase xmlTestCase, TestCase test) {
        for (XMLProperty data : xmlTestCase.getPropertyList()) {
            String xmlPropertyName = data.getName();
            String xmlPropertyData = data.getStringValue();
            test.addProperty(xmlPropertyName, xmlPropertyData);
        }
    }

    private void readTestCaseSetUpBlock(TestCase test, XMLTestCase xmlTestCase) {
        if (!xmlTestCase.isSetSetUp()) {
            return;
        }

        XMLSetUp xmlSetUp = xmlTestCase.getSetUp();
        if (xmlSetUp.isSetScript()) {
            test.setSetUpVelocityScript(xmlSetUp.getScript());
        }
    }

    /**
     * Reads a list of XML activities from inside a partnerTrack and adds them
     * to the partnerTrack.
     * 
     * @param partnerTrack
     *            the track to which the activites belong.
     * @param xmlTestCase
     * @param xmlTrack
     *            the list of XML activities
     * @param round
     * @throws SpecificationException
     */
    @SuppressWarnings("unchecked")
    private void readActivities(PartnerTrack partnerTrack, XMLTestCase xmlTestCase, XMLTrack xmlTrack, int round,
            String testDirectory) throws SpecificationException {
        List<XMLActivity> xmlActivities = ActivityUtil.getActivities(xmlTrack);

        if (xmlActivities.isEmpty()) {

            // Activity list is empty. If the test case is based on another
            // test case, check the activities of this test case.
            if (xmlTestCase.getBasedOn() != null && !"".equals(xmlTestCase.getBasedOn())) {

                // Find name of track
                String trackName = null;
                trackName = getPartnerTrackName(xmlTrack);

                // Find the first test case with has a non-empty track like ours
                XMLTestCase basedOnTestCase = findInHierarchy(xmlTestCase, trackName);
                if (basedOnTestCase != null) {
                    XMLTrack trackInNewTestCase = getPartnerTrack(basedOnTestCase, trackName);
                    if (trackInNewTestCase != null) {
                        readActivities(partnerTrack, basedOnTestCase, trackInNewTestCase, round, testDirectory);
                        return;
                    }
                }
            }

            partnerTrack.setActivities(new ArrayList<Activity>());

        } else {
            List<Activity> activities = new ArrayList<Activity>();
            for (XMLActivity event : xmlActivities) {

                Activity a;
                /*
                 * Each activity is one of the seven specified activites:
                 * ReceiveOnly, SendOnly, ReceiveSendSync, SendReceiveSync,
                 * ReceiveSendAsync, SendReceiveAsync, and Wait.
                 */
                if (event instanceof XMLWaitActivity) {
                    a = readWait(partnerTrack, activities, event, (XMLWaitActivity) event);
                } else if (event instanceof XMLReceiveActivity) {
                    a = readReceive(partnerTrack, activities, event, (XMLReceiveActivity) event);
                } else if (event instanceof XMLSendOnlyActivity) {
                    a = readSend(partnerTrack, round, testDirectory, activities, event,
                            (XMLSendOnlyActivity) event);
                } else if (event instanceof XMLTwoWayActivity) {
                    XMLTwoWayActivity op = (XMLTwoWayActivity) event;
                    a = readTwoWayActivity(partnerTrack, round, testDirectory, activities, event, op);
                } else {
                    throw new SpecificationException(
                            "No activity found when reading event list for " + partnerTrack);
                }
                a.setId(event.getId());
                a.setDependsOn(event.getDependsOn());
            }

            partnerTrack.setActivities(activities);
        }
    }

    private Activity readTwoWayActivity(PartnerTrack partnerTrack, int round, String testDirectory,
            List<Activity> activities, XMLActivity event, XMLTwoWayActivity op) throws SpecificationException {
        Activity activity = null;
        if (ActivityUtil.isActivity(op, ActivityConstant.RECEIVE_SEND_SYNC)) {
            activity = createReceiveSendSynchronous(op, partnerTrack, round, testDirectory);
        } else if (ActivityUtil.isActivity(op, ActivityConstant.SEND_RECEIVE_SYNC)) {
            activity = createSendReceiveSynchronous(op, partnerTrack, round, testDirectory);
        } else if (ActivityUtil.isActivity(op, ActivityConstant.RECEIVE_SEND_ASYNC)) {
            ReceiveSendAsync a = new ReceiveSendAsync(partnerTrack);
            fillAsyncTwoWay(a, op, round, testDirectory);
            activity = a;
        } else if (ActivityUtil.isActivity(op, ActivityConstant.SEND_RECEIVE_ASYNC)) {
            SendReceiveAsync a = new SendReceiveAsync(partnerTrack);
            fillAsyncTwoWay(a, op, round, testDirectory);
            activity = a;
        }

        activity.setAssumption(event.getAssume());
        activities.add(activity);

        return activity;
    }

    private Activity readSend(PartnerTrack partnerTrack, int round, String testDirectory, List<Activity> activities,
            XMLActivity event, XMLSendOnlyActivity xmlSend) throws SpecificationException {
        SendAsync activity = new SendAsync(partnerTrack);
        SendDataSpecification spec = createSendSpecificationFromStandalone(activity, xmlSend,
                SOAPOperationDirectionIdentifier.INPUT, round, testDirectory);

        XMLHeaderProcessor xmlHeaderProcessor = xmlSend.getHeaderProcessor();
        IHeaderProcessor proc = getHeaderProcessor(xmlHeaderProcessor);

        activity.initialize(spec, proc);
        activity.setAssumption(event.getAssume());

        activities.add(activity);
        return activity;
    }

    private Activity readReceive(PartnerTrack partnerTrack, List<Activity> activities, XMLActivity event,
            XMLReceiveActivity xmlReceive) throws SpecificationException {
        ReceiveAsync activity = new ReceiveAsync(partnerTrack);
        ReceiveDataSpecification spec = createReceiveSpecificationStandalone(activity, xmlReceive,
                SOAPOperationDirectionIdentifier.INPUT);
        activity.initialize(spec);
        activity.setAssumption(event.getAssume());

        activities.add(activity);
        return activity;
    }

    private Activity readWait(PartnerTrack partnerTrack, List<Activity> activities, XMLActivity event,
            XMLWaitActivity xmlWait) {
        Wait activity = new Wait(partnerTrack);
        activity.setWaitDuration(xmlWait.getWaitForMilliseconds());
        activity.setAssumption(event.getAssume());
        activities.add(activity);
        return activity;
    }

    private String getPartnerTrackName(XMLTrack xmlTrack) {
        String trackName;
        if (xmlTrack instanceof XMLPartnerTrack) {
            trackName = ((XMLPartnerTrack) xmlTrack).getName();
        } else {
            trackName = BPELUnitConstants.CLIENT_NAME;
        }
        return trackName;
    }

    /**
     * 
     * Creates a synchronous send/receive activity.
     * 
     * @throws IOException
     * @throws XmlException
     * 
     */
    private Activity createSendReceiveSynchronous(XMLTwoWayActivity xmlSendReceiveSync, PartnerTrack partnerTrack,
            int round, String testDirectory) throws SpecificationException {

        SendReceiveSync activity = new SendReceiveSync(partnerTrack);

        XMLSendActivity xmlSend = xmlSendReceiveSync.getSend();
        XMLReceiveActivity xmlReceive = xmlSendReceiveSync.getReceive();

        if ((xmlSend == null) || (xmlReceive == null)) {
            throw new SpecificationException(
                    "A synchronous send/receive activity must have both receive and send children.");
        }

        XMLHeaderProcessor xmlHeaderProcessor = xmlSendReceiveSync.getHeaderProcessor();
        IHeaderProcessor proc = getHeaderProcessor(xmlHeaderProcessor);

        // Always send to an input element
        SendDataSpecification sSpec = createSendSpecificationFromParent(activity, xmlSendReceiveSync, xmlSend,
                SOAPOperationDirectionIdentifier.INPUT, round, testDirectory);

        // Receive may expect a fault - or receive from an output element
        SOAPOperationDirectionIdentifier receiveDirection = SOAPOperationDirectionIdentifier.OUTPUT;
        if (xmlReceive.getFault()) {
            receiveDirection = SOAPOperationDirectionIdentifier.FAULT;
        }
        ReceiveDataSpecification rSpec = createReceiveSpecificationFromParent(activity, xmlSendReceiveSync,
                xmlReceive, receiveDirection);

        List<net.bpelunit.framework.model.test.data.DataCopyOperation> mapping = getCopyOperations(activity,
                xmlSendReceiveSync);

        activity.initialize(sSpec, rSpec, proc, mapping);
        return activity;
    }

    /**
     * Creates a synchronous receive/send activity.
     * 
     * @throws IOException
     * @throws XmlException
     * 
     */
    private Activity createReceiveSendSynchronous(XMLTwoWayActivity xmlReceiveSendSync, PartnerTrack partnerTrack,
            int round, String testDirectory) throws SpecificationException {

        ReceiveSendSync activity = new ReceiveSendSync(partnerTrack);
        activity.setAssumption(xmlReceiveSendSync.getAssume());

        XMLSendActivity xmlSend = xmlReceiveSendSync.getSend();
        XMLReceiveActivity xmlReceive = xmlReceiveSendSync.getReceive();

        if ((xmlSend == null) || (xmlReceive == null)) {
            throw new SpecificationException(
                    "A synchronous receive/send activity must have both receive and send children.");
        }

        XMLHeaderProcessor xmlHeaderProcessor = xmlReceiveSendSync.getHeaderProcessor();

        // Always receive to the input element.
        ReceiveDataSpecification rSpec = createReceiveSpecificationFromParent(activity, xmlReceiveSendSync,
                xmlReceive, SOAPOperationDirectionIdentifier.INPUT);

        // The "send" part may send a fault here, or to an output element
        SOAPOperationDirectionIdentifier sendDirection = SOAPOperationDirectionIdentifier.OUTPUT;
        if (xmlSend.getFault()) {
            sendDirection = SOAPOperationDirectionIdentifier.FAULT;
        }
        SendDataSpecification sSpec = createSendSpecificationFromParent(activity, xmlReceiveSendSync, xmlSend,
                sendDirection, round, testDirectory);

        IHeaderProcessor proc = getHeaderProcessor(xmlHeaderProcessor);
        List<net.bpelunit.framework.model.test.data.DataCopyOperation> mapping = getCopyOperations(activity,
                xmlReceiveSendSync);

        activity.initialize(sSpec, rSpec, proc, mapping);
        return activity;
    }

    /**
     * Fills an already-existing asynchronous two-way activity with the relevant
     * information from the test suite XML document.
     * 
     * @param twoWayActivity
     *            the given asynchronous activity
     * @param xmlAsyncTwoWay
     *            the XML data
     * @param round
     * @throws SpecificationException
     */
    private void fillAsyncTwoWay(TwoWayAsyncActivity twoWayActivity, XMLTwoWayActivity xmlAsyncTwoWay, int round,
            String testDirectory) throws SpecificationException {

        XMLSendActivity xmlSend = xmlAsyncTwoWay.getSend();
        XMLReceiveActivity xmlReceive = xmlAsyncTwoWay.getReceive();

        if ((xmlSend == null) || (xmlReceive == null)) {
            throw new SpecificationException(
                    "An asynchronous receive/send or send/receive activity must have both receive and send children.");
        }

        XMLHeaderProcessor xmlHeaderProcessor = xmlAsyncTwoWay.getHeaderProcessor();
        List<net.bpelunit.framework.model.test.data.DataCopyOperation> mapping = getCopyOperations(twoWayActivity,
                xmlAsyncTwoWay);

        SendAsync sendAct = new SendAsync(twoWayActivity);
        SendDataSpecification sSpec = createSendSpecificationFromStandalone(sendAct, xmlSend,
                SOAPOperationDirectionIdentifier.INPUT, round, testDirectory);
        sendAct.initialize(sSpec, null);

        ReceiveAsync receiveAct = new ReceiveAsync(twoWayActivity);
        ReceiveDataSpecification rSpec = createReceiveSpecificationStandalone(receiveAct, xmlReceive,
                SOAPOperationDirectionIdentifier.INPUT);
        receiveAct.initialize(rSpec);

        IHeaderProcessor proc = getHeaderProcessor(xmlHeaderProcessor);

        twoWayActivity.initialize(sendAct, receiveAct, proc, mapping);
    }

    // ******************** Specifications *******************

    /**
     * Creates a send specification for an asnychronous send-only. In this case,
     * service information must be stored directly on the send activity itself.
     * 
     * @throws IOException
     * @throws XmlException
     * 
     */
    private SendDataSpecification createSendSpecificationFromStandalone(Activity parentActivity,
            XMLSendActivity xmlSend, SOAPOperationDirectionIdentifier direction, int round, String testDirectory)
            throws SpecificationException {

        SOAPOperationCallIdentifier operation = getOperationCallIdentifier(parentActivity,
                getService(parentActivity, xmlSend), xmlSend.getPort(), xmlSend.getOperation(), direction);
        return createSendSpecification(parentActivity, operation, xmlSend, round, testDirectory);
    }

    /**
     * Creates a send specficiation for a synchronous send/receive. In this
     * case, service information must be stored on the send/receive activity.
     * 
     * @throws IOException
     * @throws XmlException
     * 
     */
    private SendDataSpecification createSendSpecificationFromParent(Activity parentActivity,
            XMLTwoWayActivity xmlSendReceiveSync, XMLSendActivity xmlSend,
            SOAPOperationDirectionIdentifier direction, int round, String testDirectory)
            throws SpecificationException {

        SOAPOperationCallIdentifier operation = getOperationCallIdentifier(parentActivity,
                getService(parentActivity, xmlSendReceiveSync), xmlSendReceiveSync.getPort(),
                xmlSendReceiveSync.getOperation(), direction);
        return createSendSpecification(parentActivity, operation, xmlSend, round, testDirectory);
    }

    /**
     * Creates a send specification for the given activity and operation and
     * from the given XML Send Specification.
     * 
     * @throws IOException
     * @throws XmlException
     * 
     */
    private SendDataSpecification createSendSpecification(Activity activity, SOAPOperationCallIdentifier operation,
            XMLSendActivity xmlSend, int round, String testDirectory) throws SpecificationException {

        // Namespaces
        NamespaceContext context = getNamespaceMap(xmlSend.newCursor());

        SendDataSpecification spec = new SendDataSpecification(activity, context);

        String encodingStyle = operation.getEncodingStyle();
        String targetURL = operation.getTargetURL();
        String soapAction = operation.getSOAPHTTPAction();
        ISOAPEncoder encoder = fRunner.createNewSOAPEncoder(encodingStyle);

        for (XMLProperty p : xmlSend.getTransportOptionList()) {
            spec.putProtocolOption(p.getName(), p.getStringValue());
        }

        // "delay" attribute
        if (xmlSend.isSetDelay() && xmlSend.isSetDelaySequence()) {
            throw new SpecificationException(
                    "Send Element can only have exactly one of {delay, delaySequence}, and not both");
        }
        final String delayExpression = xmlSend.getDelay();

        /*
         * Import namespaces in the BPTS file to the root elements of the <data>
         * or <template> element, and convert the <template> contents to text.
         */
        Element rawDataRoot = null;
        String templateText = null;
        try {
            if (xmlSend.isSetData()) {
                rawDataRoot = getLiteralDataForSend(xmlSend.getData(), testDirectory);
            }
            if (xmlSend.isSetTemplate()) {
                templateText = getTemplateText(testDirectory, xmlSend.getTemplate());
            }
        } catch (Exception ex) {
            throw new SpecificationException(
                    "There was a problem while interpreting the 'src' attribute in activity " + activity, ex);
        }

        /*
         * Get round data
         */
        String delaySequence = xmlSend.getDelaySequence();
        List<Double> sequence = getRoundInformation(delaySequence);
        double currentDelay = 0;
        if (sequence != null && sequence.size() > round) {
            currentDelay = sequence.get(round);
        }

        // If the user hasn't specified any fault code or string, use these
        // default values
        final QName faultCode = xmlSend.isSetFaultcode() ? xmlSend.getFaultcode()
                : BPELUnitConstants.SOAP_FAULT_CODE_CLIENT;
        final String faultString = xmlSend.isSetFaultstring() ? xmlSend.getFaultstring()
                : BPELUnitConstants.SOAP_FAULT_DESCRIPTION;
        if (activity instanceof ReceiveSendSync) {
            // This send specification will be used to send back
            // a response to a HTTP request inside the same channel
            // It does not need targetURL and soapAction.
            spec.initialize(operation, currentDelay, delayExpression, null, null, encodingStyle, encoder,
                    rawDataRoot, templateText, faultCode, faultString);
        } else {
            spec.initialize(operation, currentDelay, delayExpression, targetURL, soapAction, encodingStyle, encoder,
                    rawDataRoot, templateText, faultCode, faultString);
        }

        return spec;
    }

    private String getTemplateText(String testDirectory, final XMLAnyElement xmlTemplate)
            throws IOException, SpecificationException, XmlException {
        String templateText;
        if (xmlTemplate.isSetSrc()) {
            // 'src' attribute in <template> - load as raw text, *not*
            // XML - much less escaping involved
            // Cannot reuse namespaces in .bpts - user must set
            // namespaces in the .vm (same as when loading an external
            // XML file)
            final StringBuffer sbuf = new StringBuffer();
            sbuf.append("<");
            sbuf.append(BPELUnitUtil.DUMMY_ELEMENT_NAME);
            sbuf.append(">");
            sbuf.append(FileUtils.readFileToString(new File(testDirectory, xmlTemplate.getSrc())));
            sbuf.append("</");
            sbuf.append(BPELUnitUtil.DUMMY_ELEMENT_NAME);
            sbuf.append(">");
            templateText = sbuf.toString();
        } else if (hasChildElements(xmlTemplate)) {
            // We have child elements: import their namespaces and then dump them as text
            Element templateRoot = copyAsRootWithNamespaces(xmlTemplate);
            templateText = XmlObject.Factory.parse(templateRoot).xmlText();
        } else {
            /*
             * No child elements: the <template> is probably using a
             * CDATA section to store a standalone template that is not
             * valid XML, so use the text as-is.
             */
            final XmlCursor c = xmlTemplate.newCursor();
            String chars = "";
            if (!c.toFirstContentToken().isFinish()) {
                chars = c.getChars();
            }
            templateText = wrapWithDummyElement(chars);
            c.dispose();
        }
        return templateText;
    }

    private String wrapWithDummyElement(final String text) {
        String templateText;
        final StringBuffer sbuf = new StringBuffer();
        sbuf.append("<");
        sbuf.append(BPELUnitUtil.DUMMY_ELEMENT_NAME);
        sbuf.append(">");
        sbuf.append(text);
        sbuf.append("</");
        sbuf.append(BPELUnitUtil.DUMMY_ELEMENT_NAME);
        sbuf.append(">");
        templateText = sbuf.toString();
        return templateText;
    }

    private boolean hasChildElements(XmlObject xmlObject) {
        final XmlCursor c = xmlObject.newCursor();
        try {
            return c.toFirstChild();
        } finally {
            c.dispose();
        }
    }

    private Element getLiteralDataForSend(XMLAnyElement dataInSend, String testDirectory)
            throws SpecificationException {
        if (dataInSend == null) {
            return null;
        }

        Element rawDataRoot;
        if (dataInSend.isSetSrc()) {
            // src attribute in <data>
            final String path = dataInSend.getSrc();
            try {
                rawDataRoot = copyAsRootWithNamespaces(XmlObject.Factory.parse(new File(testDirectory, path)));
            } catch (Exception e) {
                throw new SpecificationException("Error while importing data: " + e.getMessage(), e);
            }
        } else {
            rawDataRoot = copyAsRootWithNamespaces(dataInSend);
        }
        return rawDataRoot;
    }

    private Element copyAsRootWithNamespaces(XmlObject xmlData) throws SpecificationException {
        final Element rawDataRoot = BPELUnitUtil.generateDummyElementNode();
        final Document rawDataDoc = rawDataRoot.getOwnerDocument();

        final XmlCursor dataCursor = xmlData.newCursor();
        if (dataCursor.toFirstChild()) {
            do {
                // Skip comments
                if (dataCursor.isComment()) {
                    continue;
                }

                if (dataCursor.isContainer()) {
                    // Import elements, including their namespace nodes
                    final Node original = dataCursor.getDomNode();
                    final Node imported = rawDataDoc.importNode(original, true);
                    rawDataRoot.appendChild(imported);
                }
            } while (dataCursor.toNextSibling());
        }

        dataCursor.dispose();
        return rawDataRoot;
    }

    /**
     * Creates a receive data specification for an asynchronous receive. In this
     * case, the service information must be stored on the asynchronous receive
     * itself.
     */
    private ReceiveDataSpecification createReceiveSpecificationStandalone(Activity parentActivity,
            XMLReceiveActivity xmlReceive, SOAPOperationDirectionIdentifier direction)
            throws SpecificationException {

        SOAPOperationCallIdentifier operation = getOperationCallIdentifier(parentActivity,
                getService(parentActivity, xmlReceive), xmlReceive.getPort(), xmlReceive.getOperation(), direction);
        return createReceiveSpecification(parentActivity, operation, xmlReceive);
    }

    /**
     * Creates a receive data specification for a synchronous receive/send. In
     * this case, service information must be stored on the receive/send
     * activity.
     */
    private ReceiveDataSpecification createReceiveSpecificationFromParent(Activity parentActivity,
            XMLTwoWayActivity xmlReceiveSendSync, XMLReceiveActivity xmlReceive,
            SOAPOperationDirectionIdentifier direction) throws SpecificationException {

        SOAPOperationCallIdentifier operation = getOperationCallIdentifier(parentActivity,
                getService(parentActivity, xmlReceiveSendSync), xmlReceiveSendSync.getPort(),
                xmlReceiveSendSync.getOperation(), direction);
        return createReceiveSpecification(parentActivity, operation, xmlReceive);
    }

    /**
     * Creates a receive specification for the given activity and operation from
     * the information stored in the XML Receive.
     */
    private ReceiveDataSpecification createReceiveSpecification(Activity activity,
            SOAPOperationCallIdentifier operation, XMLReceiveActivity xmlReceive) throws SpecificationException {

        // Namespaces
        NamespaceContext context = getNamespaceMap(xmlReceive.newCursor());

        ReceiveDataSpecification spec = new ReceiveDataSpecification(activity, context);

        String encodingStyle = operation.getEncodingStyle();
        ISOAPEncoder encoder = fRunner.createNewSOAPEncoder(encodingStyle);

        // get conditions
        List<XMLCondition> xmlConditionList = xmlReceive.getConditionList();
        List<ReceiveCondition> cList = new ArrayList<ReceiveCondition>();
        if (xmlConditionList != null) {
            for (XMLCondition xmlCondition : xmlConditionList) {
                cList.add(new ReceiveCondition(spec, xmlCondition.getExpression(), xmlCondition.getTemplate(),
                        xmlCondition.getValue()));
            }
        }
        addConditionsFromConditionGroups(xmlReceive.getConditionGroupList(), spec, cList);

        // get data extraction requests
        final List<XMLDataExtraction> xmlDataExtractionList = xmlReceive.getDataExtractionList();
        final List<DataExtraction> deList = readDataExtractionElements(spec, xmlDataExtractionList);

        // Add fault code and string. These will be only checked if this message
        // is a fault and if
        // they are not null.
        QName faultCode = xmlReceive.getFaultcode();
        String faultString = xmlReceive.getFaultstring();

        spec.initialize(operation, encodingStyle, encoder, cList, deList, faultCode, faultString);
        return spec;
    }

    private List<DataExtraction> readDataExtractionElements(DataSpecification spec,
            final List<XMLDataExtraction> xmlDataExtractionList) throws SpecificationException {
        List<DataExtraction> deList = new ArrayList<DataExtraction>();
        if (xmlDataExtractionList != null) {
            for (XMLDataExtraction xmlDE : xmlDataExtractionList) {
                deList.add(new DataExtraction(spec, xmlDE.getExpression(), xmlDE.getVariable(), xmlDE.getScope(),
                        xmlDE.getType()));
            }
        }
        return deList;
    }

    private void addConditionsFromConditionGroups(List<String> conditionGroupNames, DataSpecification spec,
            List<ReceiveCondition> cList) throws SpecificationException {
        if (conditionGroupNames != null) {
            for (String cgName : conditionGroupNames) {
                XMLConditionGroup cg = conditionGroups.get(cgName);
                addConditionsFromConditionGroup(spec, cList, cg);

            }
        }
    }

    private void addConditionsFromConditionGroup(DataSpecification spec, List<ReceiveCondition> cList,
            XMLConditionGroup cg) throws SpecificationException {

        List<XMLCondition> conditionList = resolveConditionsForGroup(cg);

        for (XMLCondition xmlCondition : conditionList) {
            cList.add(new ReceiveCondition(spec, xmlCondition.getExpression(), xmlCondition.getTemplate(),
                    xmlCondition.getValue()));
        }
    }

    // *********** HELPERS *******************

    private List<XMLCondition> resolveConditionsForGroup(XMLConditionGroup cg) {
        List<XMLConditionGroup> cgs = getInheritanceBranch(cg);

        List<XMLCondition> conditions = new ArrayList<XMLCondition>();
        for (XMLConditionGroup currentCG : cgs) {
            if (currentCG.getConditionList() != null) {
                conditions.addAll(currentCG.getConditionList());
            }
        }

        return conditions;
    }

    private List<XMLConditionGroup> getInheritanceBranch(XMLConditionGroup cg) {
        List<XMLConditionGroup> cgs = new ArrayList<XMLConditionGroup>();

        XMLConditionGroup currentCG = cg;
        cgs.add(currentCG);
        while (currentCG.getInheritFrom() != null) {
            currentCG = conditionGroups.get(currentCG.getInheritFrom());
            cgs.add(0, currentCG);
        }

        return cgs;
    }

    private List<DataCopyOperation> getCopyOperations(Activity activity, XMLTwoWayActivity xmlTwoWayType)
            throws SpecificationException {

        ArrayList<DataCopyOperation> copyDataOperations = new ArrayList<DataCopyOperation>();
        XMLMapping xmlMapping = xmlTwoWayType.getMapping();
        if (xmlMapping != null) {
            List<XMLCopy> xmlCopyList = xmlMapping.getCopyList();
            if (xmlCopyList != null) {
                for (XMLCopy xmlCopy : xmlCopyList) {
                    String xmlCopyFrom = xmlCopy.getFrom();
                    String xmlCopyTo = xmlCopy.getTo();
                    if ((xmlCopyFrom == null) || (xmlCopyTo == null)) {
                        throw new SpecificationException(
                                "Copy operations need both copy-from and copy-to specifications.");
                    }

                    copyDataOperations.add(new DataCopyOperation(activity, xmlCopyFrom, xmlCopyTo));
                }
            }
        }

        return copyDataOperations;
    }

    private IHeaderProcessor getHeaderProcessor(XMLHeaderProcessor xmlHeaderProcessor)
            throws SpecificationException {

        if (xmlHeaderProcessor == null) {
            return null;
        }

        String xmlHeaderProcessorName = xmlHeaderProcessor.getName();
        if (xmlHeaderProcessorName == null) {
            throw new SpecificationException("Header Processor needs a name.");
        }

        List<XMLProperty> propertyList = xmlHeaderProcessor.getPropertyList();

        IHeaderProcessor proc = fRunner.createNewHeaderProcessor(xmlHeaderProcessorName);
        if (propertyList != null) {
            for (XMLProperty property : propertyList) {
                String xmlPropertyName = property.getName();
                String xmlPropertyData = property.getStringValue();
                if ((xmlPropertyName == null) || (xmlPropertyData == null)) {
                    throw new SpecificationException("Properties in Header Processor " + xmlHeaderProcessorName
                            + " need both property name and value.");
                }

                proc.setProperty(xmlPropertyName, xmlPropertyData);
            }
        }
        return proc;
    }

    private SOAPOperationCallIdentifier getOperationCallIdentifier(Activity activity, QName service, String port,
            String operation, SOAPOperationDirectionIdentifier direction) throws SpecificationException {

        Partner partner = (Partner) activity.getPartner();

        if (service == null) {
            throw new SpecificationException("Expected a service specification in activity " + activity.getName()
                    + " (PartnerTrack for partner " + partner + ").");
        }
        if (port == null) {
            throw new SpecificationException("Expected a port specification in activity " + activity.getName()
                    + " (PartnerTrack for partner " + partner + ").");
        }
        if (operation == null) {
            throw new SpecificationException("Expected a operation specification in activity " + activity.getName()
                    + " (PartnerTrack for partner " + partner + ").");
        }

        return partner.getOperation(service, port, operation, direction);
    }

    private QName getService(Activity parentActivity, XMLSoapActivity xmlActivity) throws SpecificationException {
        QName service = null;
        try {
            service = xmlActivity.getService();
        } catch (Exception e) {
        }
        if (service == null) {
            throw new SpecificationException("Could not find service for activity " + parentActivity.getName()
                    + ": not specified or wrong prefix.");
        }
        return service;
    }

    /**
     * Get namespaces from send object (include all those specified higher up
     * the chain). We could get these from the top-level element, if the only
     * way of specifiying the test suite document were the tool support...
     * 
     */
    private NamespaceContextImpl getNamespaceMap(XmlCursor newCursor) {

        Map<String, String> namespaces = new HashMap<String, String>();
        newCursor.getAllNamespaces(namespaces);
        NamespaceContextImpl context = new NamespaceContextImpl();
        for (String prefix : namespaces.keySet()) {
            context.setNamespace(prefix, namespaces.get(prefix));
        }
        return context;
    }

    /**
     * Finds a track with a non-empty partner track in the test case inheritance
     * hierarchy
     * 
     */
    private XMLTestCase findInHierarchy(XMLTestCase xmlTestCase, String partnerTrackName) {

        XmlCursor cursor = xmlTestCase.newCursor();
        try {
            String basedOn = xmlTestCase.getBasedOn();
            if (cursor.toParent()) {
                XMLTestCasesSection section = (XMLTestCasesSection) cursor.getObject();
                for (XMLTestCase xmlTestCaseFor : section.getTestCaseList()) {
                    if (basedOn.equals(xmlTestCaseFor.getName())) {
                        if (hasNonEmptyPartnerTrack(xmlTestCaseFor, partnerTrackName)) {
                            return xmlTestCaseFor;
                        } else if (xmlTestCaseFor.getBasedOn() != null && !"".equals(xmlTestCaseFor.getBasedOn())) {
                            return findInHierarchy(xmlTestCaseFor, partnerTrackName);
                        }
                    }
                }
            }
            return null;
        } finally {
            cursor.dispose();
        }
    }

    private boolean hasNonEmptyPartnerTrack(XMLTestCase xmlTestCase, String partnerTrackName) {

        XMLTrack track = getPartnerTrack(xmlTestCase, partnerTrackName);
        if (track != null) {
            return !ActivityUtil.getActivities(track).isEmpty();
        }

        return false;
    }

    private XMLTrack getPartnerTrack(XMLTestCase xmlTestCase, String trackName) {
        XMLTrack track = null;
        if (trackName.equalsIgnoreCase(BPELUnitConstants.CLIENT_NAME)) {
            track = xmlTestCase.getClientTrack();
        } else {
            for (XMLPartnerTrack pTrack : xmlTestCase.getPartnerTrackList()) {
                if (trackName.equals(pTrack.getName())) {
                    track = pTrack;
                    break;
                }
            }
        }
        return track;
    }

    private List<Double> getRoundInformation(String roundsAsText) {
        List<Double> list = new ArrayList<Double>();
        if (roundsAsText != null && !"".equals(roundsAsText)) {
            String[] values = roundsAsText.split(",");
            for (int j = 0; j < values.length; j++) {
                try {
                    list.add(Double.parseDouble(values[j].trim()));
                } catch (NumberFormatException e) {
                    return null;
                }
            }
            return list;
        }
        return null;
    }

}