package com.fujitsu.iflowqa.testframe;

import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;

import com.fujitsu.iflow.model.util.ModelException;
import com.fujitsu.iflow.model.workflow.DataItem;
import com.fujitsu.iflow.model.workflow.DataItemRef;
import com.fujitsu.iflow.model.workflow.DirectoryServices;
import com.fujitsu.iflow.model.workflow.Filter;
import com.fujitsu.iflow.model.workflow.JavaAction;
import com.fujitsu.iflow.model.workflow.JavaActionSet;
import com.fujitsu.iflow.model.workflow.Node;
import com.fujitsu.iflow.model.workflow.Plan;
import com.fujitsu.iflow.model.workflow.ProcessInstance;
import com.fujitsu.iflow.model.workflow.TimerStatus;
import com.fujitsu.iflow.model.workflow.WFAdminSession;
import com.fujitsu.iflow.model.workflow.WFObjectFactory;
import com.fujitsu.iflow.model.workflow.WFObjectList;
import com.fujitsu.iflow.model.workflow.WorkItem;

/**
 * The purpose of this abstract class is to provide utility methods which are
 * commonly used when creating
 */

public abstract class BPMTestBase implements TestSet {

    /**
     * @uml.property name="tr"
     * @uml.associationEnd multiplicity="(0 1)"
     */
    protected TestRecorder tr;

    protected WFAdminSession wfSession;

    protected String server;

    protected String username;

    protected String password;

    protected String RESULT_UDA = "TestResult";

    protected String TIMER_UDA = "TimerTest";

    protected String RESULT_PASSED = "Passed";

    protected String RESULT_FAILED = "Failed";

    protected String PEXCEPTION_UDA = "__ErrorMessage";

    protected String lastImportedPlan = null;

    protected String filePath = null;

    // holds a list of ids of templates that have been added so that they
    // can be removed again at the end
    protected ArrayList importedTemplateIds;

    // empty constructor is needed for dynamic class load.
    public BPMTestBase() {
        importedTemplateIds = new ArrayList();
    }

    protected void init(TestRecorder newTr) throws Exception {
        if (newTr == null) {
            throw new Exception(
                    "BPMTestBase.init must have a test recorder passed to it.");
        }
        try {
            tr = newTr;
            wfSession = null;
            server = tr.getRequiredProperty("servername");
            username = tr.getRequiredProperty("username");
            password = tr.getRequiredProperty("password");
        } catch (Exception e) {
            throw new ExceptionWrapper("BPMTestBase.init failed.", e);
        }
    }

    /**
     * Creates a wfSession object, initializes it for an application and logs
     * the user in. (wfSession != null) is an indication that you are logged in.
     */
    protected void login() throws Exception {
        if (tr == null) {
            throw new Exception(
                    "BPMTestBase.init must be called before login can be called");
        }
        try {
            WFAdminSession newSession = WFObjectFactory.getWFAdminSession();
            newSession.initForApplication(null, tr.getProps());
            newSession.logIn(server + "Flow", username, password);
            wfSession = newSession;
        } catch (Exception e) {
            throw new ExceptionWrapper(
                    "Exception caught in BPMTestBase.logIn(server=" + server
                            + ", user=" + username + ", password=" + password
                            + ")", e);
        }
    }

    /**
     * Performs a logout if it is needed. If you are alread logged out, then it
     * does not complain. after logOut, wfSession is set to null, so you can
     * check if you are already logged in by checking if session is null.
     * (wfSession == null) is an indication that you are logged out.
     */
    public void logOut() throws Exception {
        try {
            if (wfSession != null) {
                wfSession.logOut();
                wfSession = null;
            }
        } catch (Exception e) {
            throw new ExceptionWrapper("Exception in BPMTestBase.logOut", e);
        }
    }

    /**
     * Same as above, but useful for cleanup code that wants to logout if
     * possible, but ignore any problems that arise.
     */
    public void logOutWithoutException() {
        try {
            logOut();
        } catch (Exception e) {
            tr.log("Logout failed with exception: " + e.toString());
        }
    }

    /**
     * Some of the model API return arrays of object, but return null instead of
     * an empty array. This causes a lot of null pointer exceptions in the test
     * programs when a test fails. Since the null pointer exception is not very
     * informative, this routine is an easy way to do better. Use this for tests
     * that are "supposed" to always return some objects, and you do not expect
     * it to ever return an empty array or null. This just checks these
     * situations and throws a more accptable exception.
     */
    protected void AssertObjectArrayNotEmpty(Object[] objArray)
            throws Exception {
        if (objArray == null) {
            throw new Exception("Got a null value for the object array.");
        }
        if (objArray.length == 0) {
            throw new Exception("Got an empty object array.");
        }
    }

    /**
     * A common pattern in writing tests is to get a string value, or to compose
     * a string from non-string structures, and then compare that string to a
     * known property. If the program runs correctly, it will always return the
     * same string value. Use this routine to pass in the actual value and the
     * expected value. It will compare, and then record success or failure. The
     * setting, should be a short description of what the test is testing, and
     * should be as unique as possible so that if it fails the setting can be an
     * indicator of what part of the program was running at the time.
     *
     * This handles null values correctly for either actual or expected now.
     */
    protected void testAndReport(String setting, String actual, String expected) {
        if (actual == null && expected == null) {
            tr.markPassed(setting);
        }
        else if (actual == null) {
            tr.markFailed(setting, "Expected '" + expected
                    + "' but got null instead.");
        }
        else if (expected == null) {
            tr.markFailed(setting, "Expected null but got '" + actual
                    + "' instead.");
        }
        else if (expected.equalsIgnoreCase(actual)) {
            tr.markPassed(setting);
        }
        else {
            //support for UTF-8 display ...
            StringBuffer msg = new StringBuffer();
            msg.append("Expected ");
            appendLiteral(msg, expected);
            msg.append(" but got ");
            appendLiteral(msg, actual);
            msg.append(" instead.");
            tr.markFailed(setting, msg.toString());
        }
    }

    /**
     * Same as above, but with integers.
     */
    protected void testAndReport(String setting, int actual, int expected) {
        if (expected == actual) {
            tr.markPassed(setting);
        } else {
            tr.markFailed(setting, "Expected '" + expected + "' but got '"
                    + actual + "' instead.");
        }
    }

    static final String hex = "0123456789ABCDEF";

    /**
     * appends a value to the string buffer, but any character outside of the
     * printable range are converted to 4-digit hex values.
     */
    protected void appendLiteral(StringBuffer msg, String value) {
        if (value == null) {
            msg.append("(null)");
        }
        msg.append("\"");
        for (int i = 0; i < value.length(); i++) {
            char ch = value.charAt(i);
            if (ch == '\"' || ch == '\\') {
                msg.append('\\');
                msg.append(ch);
            } else if (ch >= ' ' && ch < 256) {
                msg.append(ch);
            } else {
                int runner = (int) ch;
                int digit4 = runner % 16;
                runner = ch / 16;
                int digit3 = runner % 16;
                runner = runner / 16;
                int digit2 = runner % 16;
                runner = runner / 16;
                int digit1 = runner % 16;
                msg.append("\\u");
                msg.append(hex.charAt(digit1));
                msg.append(hex.charAt(digit2));
                msg.append(hex.charAt(digit3));
                msg.append(hex.charAt(digit4));
            }
        }
        msg.append("\"");
    }

    /**
     * returns a literal expression with any character outside of the printable
     * range converted to 4-digit hex values.
     */
    protected String getLit(String value) {
        StringBuffer buff = new StringBuffer();
        appendLiteral(buff, value);
        return buff.toString();
    }

    /**
     * Import a collection of templates by name (given a directory) and return
     * the array of plan ids.
     */
    protected long[] importTemplates(String srcDir, String[] templateNames)
            throws Exception {

        File dir = new File(srcDir);

        if (!dir.exists()) {
            throw new Exception(
                    "Missing directory ("
                            + srcDir
                            + ") that BPMTestBase.importTemplates has been asked to import from.  Check to see if your test configuration is correct.");
        }
        int numberToImport = templateNames.length;
        long[] planIds = new long[numberToImport];

        tr.log("importing " + numberToImport + " templates from dir="
                + dir.getCanonicalPath());
        for (int i = 0; i < numberToImport; i++) {
            File f = new File(dir, templateNames[i]);
            tr.log("importing template=" + f.getCanonicalPath());
            Plan p = WFObjectFactory.getPlan();
            p.setWFSession(wfSession);
            FileInputStream fis = new FileInputStream(f);
            p.convertFromXPDL(fis);
            fis.close();
            p = p.createProcessDef();
            p.startEdit();
            fixUpPlan(p);
            p.commitEdit();
            importedTemplateIds.add(new Long(p.getId()));
            planIds[i] = p.getId();
        }
        return planIds;
    }

    private void fixUpPlan(Plan p) throws Exception {
        // now search for all java actions, and change the location
        // from c:/ibpm_attachments to whatever specified in javaActionLocation
        // config setting
        // for solaris this is probably /opt2/fujitsu/iflow/dmsroot

        /*
         * SecF Code To make single test case for windows and solaris
         */

        /*
         * Code to handle WSA cases where entries in the XPDL are port specific
         * and needs to be changed to machine specific.Chnges also for Windows
         * and Solaris.
         */
        String fileName = p.getName();
        String localPath = tr.getProperty("buildPath", "localhost:49950");
        if ((fileName.indexOf("WSA")) != -1) {

            /*
             * To change the ReqUda value to Build specific value
             */
            DataItemRef[] wsaData = p.getDataItemRefs();
            for (int wa = 0; wa < wsaData.length; wa++) {
                if (wsaData[wa].getName().equalsIgnoreCase("ReqUda")) {
                    String reqOld = wsaData[wa].getValue();
                    String reqNew = reqOld.replaceAll("localhost:9004",
                            localPath);
                    wsaData[wa].setValue(reqNew);

                }
            }
            /*
             * To change the Build specific path in commit script
             */
            String wsaCoOld = p.getCommitScript();
            if (wsaCoOld != null) {
                String wsaCoNew = wsaCoOld.replaceAll("localhost:9004",
                        localPath);
                p.setCommitScript(wsaCoNew);
            }
            /*
             * Code to handle the Prologue Action script on the node which is
             * Build specific.
             */
            Node[] wsaNodes = p.getNodes();
            for (int wn = 0; wn < wsaNodes.length; wn++) {
                if (wsaNodes[wn].supportPrologueScript()) {
                    String prScriptOld = wsaNodes[wn].getPrologueScript();
                    String prScriptNew = prScriptOld.replaceAll(
                            "localhost:9004", localPath);
                    wsaNodes[wn].setPrologueScript(prScriptNew);

                }
            }
        }

        /*
         * Additional code for the timer scripts added by SecF. Here the XPDL
         * file is checked to see if it contains the DMS path in the timer
         * attribute it is replaced with the dmsRoot entry of the test.conf
         * file.
         */
        String jAcLocation = tr.getProperty("dmsRoot", "C:/ibpm_attachments/");

        DataItemRef[] dataItem = p.getDataItemRefs();
        for (int t = 0; t < dataItem.length; t++) {
            if (dataItem[t].getName().startsWith("__ptmr")
                    || dataItem[t].getName().startsWith("__atmr")) {
                String oldDiValue = (dataItem[t].getValue());
                String newDiValue = oldDiValue.replaceAll(
                        "C:/ibpm_attachments/", jAcLocation);
                //Code for WSA cases only
                if (fileName.indexOf("WSA") != -1) {
                    newDiValue = newDiValue.replaceAll("localhost:9004",
                            localPath);
                }
                dataItem[t].setValue(newDiValue);
            }
        }

        if (jAcLocation != null) {
            Node[] nodes = p.getNodes();
            for (int n = 0; n < nodes.length; n++) {
                cleanNodePaths(nodes[n], jAcLocation);

            }
            JavaActionSet jas = cleanJASPaths(
                    p.getProcOwnerRoleJavaActionSet(), jAcLocation);
            if (jas != null) {
                p.setProcOwnerRoleJavaActionSet(jas);
            }
            jas = cleanJASPaths(p.getInitJavaActionSet(), jAcLocation);
            if (jas != null) {
                p.setInitJavaActionSet(jas);
            }
            jas = cleanJASPaths(p.getCommitJavaActionSet(), jAcLocation);
            if (jas != null) {
                p.setCommitJavaActionSet(jas);
            }
        }

        /*
         * Code to handle AGT001 which contains path that differ for Windows and
         * Solaris.
         */
        if ((fileName.indexOf("AGT29")) != -1) {
            DataItemRef[] agtData = p.getDataItemRefs();
            for (int lop = 0; lop < agtData.length; lop++) {
                if (agtData[lop].getName().equalsIgnoreCase("ActLogFile")) {
                    if (jAcLocation.startsWith("/opt2")) {
                        String oldAcPath = agtData[lop].getValue();
                        String newAcPath = oldAcPath
                                .replaceAll("C:/", "/opt2/");
                        agtData[lop].setValue(newAcPath);
                    }

                }
                if (agtData[lop].getName().equalsIgnoreCase("ExpLogFile")) {
                    String oldAgPath = agtData[lop].getValue();
                    String newAgPath = oldAgPath.replaceAll(
                            "C:/ibpm_attachments/", jAcLocation);
                    agtData[lop].setValue(newAgPath);
                }

            }

        }
        /*
         * New code added by SecF, To handle FTP test cases As in FTP test cases
         * they need to be handled seperately and two entries need to be changed
         * for Windows and Solaris cases. One for REMOTE_FTP_DIRECTORY and the
         * other for dmsRoot entry in the script.
         */

        /*
         * For FTP cases the name of the file should contain "FTP" Code to
         * handle the FTP_REMOTE_DIRECTORY entry
         */
        if ((fileName.indexOf("FTP")) != -1) {
            String ftpPath = tr.getProperty("FtpRootPath", "\\user1");
            if (ftpPath != null) {
                DataItemRef[] ftpData = p.getDataItemRefs();
                for (int td = 0; td < ftpData.length; td++) {
                    if (ftpData[td].getName().equalsIgnoreCase(
                            "REMOTE_FTP_DIRECTORY")) {
                        if (ftpData[td].getValue().endsWith("user1")) {
                            ftpData[td].setValue(ftpPath);
                            tr.log("FTP Root Path set to " + ftpPath);
                        }
                    }
                }
                /* Code to handle the dmsRoot entry in the script */
                Node[] nodesf = p.getNodes();
                for (int ni = 0; ni < nodesf.length; ni++) {
                    if (nodesf[ni].supportPrologueScript()) {
                        String scriptPrOld = nodesf[ni].getPrologueScript();
                        String scriptPrNew = scriptPrOld
                                .replaceAll("\\\\", "/");
                        scriptPrNew = scriptPrNew.replaceAll(
                                "C://ibpm_attachments//", jAcLocation);
                        nodesf[ni].setPrologueScript(scriptPrNew);
                    }
                }
            }
        }
    }

    private void cleanNodePaths(Node aNode, String newLoc) throws Exception {

        if (aNode.supportPrologueJavaActionSet()) {
            JavaActionSet jas = cleanJASPaths(aNode.getPrologueJavaActionSet(),
                    newLoc);

            if (jas != null) {
                aNode.setPrologueJavaActionSet(jas);
            }
        }
        if (aNode.supportEpilogueJavaActionSet()) {
            JavaActionSet jas = cleanJASPaths(aNode.getEpilogueJavaActionSet(),
                    newLoc);
            if (jas != null) {
                aNode.setEpilogueJavaActionSet(jas);
            }
        }
        if (aNode.supportRoleJavaActionSet()) {
            JavaActionSet jas = cleanJASPaths(aNode.getRoleJavaActionSet(),
                    newLoc);
            if (jas != null) {
                aNode.setRoleJavaActionSet(jas);
            }
        }
    }

    private JavaActionSet cleanJASPaths(JavaActionSet jas, String newLoc)
            throws Exception {

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

        /*
         * To rule down different test cases for windows and solaris. Here the
         * path is read in the method and replaced where and when needed.
         */
        JavaActionSet returnJas = null;
        JavaAction[] jaa = jas.getJavaActions();

        /*
         * SecF Code Here the old path is read and the class path and the
         * InputUDA entry is replaced to the current dmsRoot Path.
         */
        String dmsPath = tr.getProperty("dmsRoot", "C:/ibpm_attachments/");
        for (int i = 0; i < jaa.length; i++) {
            JavaAction anAction = jaa[i];

            /* Class Path set to new Path with new dmsRoot Path */
            String oldPath = anAction.getClassPath();
            /* Check for back slashes */
            if (oldPath.indexOf('\\') != -1) {
                tr.log("##BackSlash Exixts in the Class Path##");
                throw new Exception("BackSlash Exixts in the Class Path");
            }
            String newPath = oldPath
                    .replaceAll("C:/ibpm_attachments/", dmsPath);
            anAction.setClassPath(newPath);

            /*
             * Code for changes to be made for WSA cases
             */

            String locPath = tr.getProperty("buildPath", "localhost:49950");
             /*
             * Input UDA entry updated with respect to new dmsRoot Path & build
             * specific changes for WSA test cases.
             */

            String inputUdaOld = anAction.getArgumentsUDANames();
            /* Check for back slashes */
            if (inputUdaOld.indexOf('\\') != -1) {
                tr.log("##BackSlash Exixts in the InputUDA tag##");
                throw new Exception("BackSlash Exixts in the InputUDA tag");
            }
            String inputUdaNew = inputUdaOld.replaceAll("C:/ibpm_attachments/",
                    dmsPath);
            //The line below is useful for WSA test cases
            inputUdaNew = inputUdaNew.replaceAll("localhost:9004", locPath);
            anAction.setArgumentsUDANames(inputUdaNew);

            returnJas = jas;

        }
        // must be set back on there because pass-by-value semantics
        if (returnJas != null) {
            returnJas.setJavaActions(jaa);
        }
        return returnJas;
    }

    /**
     * For cleanup purposes. Run through all loaded plans, and remove them.
     * SHOULD have a better name, "close" is not descriptive enough
     */
    protected void close() throws Exception {
        for (Iterator itr = importedTemplateIds.iterator(); itr.hasNext();) {
            long planid = ((Long) itr.next()).longValue();
            removeProcsAndPlan(planid);
        }

        logOut();
    }

    /**
     * For a given plan id, this method finds all instances on this plan, and
     * terminates then, then finally deletes the plan itself. For cleanup
     * purposes.
     */
    protected void removeProcsAndPlan(long planId) throws Exception {

        WFObjectList lstr = null;
        try {
            lstr = WFObjectFactory.getWFObjectList(wfSession);

            lstr.addFilter(WFObjectList.LISTFIELD_PLAN_ID,
                    WFObjectList.SQLOP_EQUALTO, "'" + planId + "'");
            lstr.openBatchedList(Filter.AllProcesses);
            for (Object[] procs = lstr.getNextBatch(10); procs != null; procs = lstr
                    .getNextBatch(10)) {
                for (int i = 0; i < procs.length; i++) {
                    ProcessInstance one = ((ProcessInstance) procs[i]);
                    wfSession.deleteProcessInstance(one.getId());
                }
            }
            wfSession.deletePlan(planId);
        } catch (Exception ex) {
            throw new ExceptionWrapper(
                    "Exception while deleting processes  & template for Template Id="
                            + planId, ex);
        } finally {
            if (lstr != null) {
                try {
                    lstr.closeList();
                } catch (ModelException giveup) {
                }
            }
        }
    }

    public void checkResultUDA(ProcessInstance pi) throws Exception {
        try {

            DataItem di = pi.getDataItem(RESULT_UDA);
            if (di == null) {
                tr.markFailed("checkResultUDA", "The UDA named '" + RESULT_UDA
                        + "' is missing.");
            } else {

                testAndReport("checkResultUDA", di.getValue(), RESULT_PASSED);
            }

        } catch (Exception e) {
            throw new ExceptionWrapper(
                    "checkResultUDA() - Possibly because of ProcessInstance.getDataItem()",
                    e);
        }
    }

    public boolean checkNegativeTestCase(ProcessInstance pi) throws Exception {
        //to refresh pi
        pi = WFObjectFactory.getProcessInstance(pi.getId(), wfSession);
        DataItem expDi = pi.getDataItem(PEXCEPTION_UDA);
        if (expDi == null) {
            return false;
        }
        DataItem actDi = pi.getDataItem("_ProcessException");
        if (actDi == null) {
            throw new Exception(
                    "Negative test case identified because 'ErrorMessage' is defined, but invalid negative test because '_ProcessException' is not defined.");
        }

        String expectedValue = expDi.getValue();
        String actualValue = actDi.getValue();

        if (actualValue.indexOf(expectedValue) != -1) {
            markPassed("Test case passed");
        } else {
            markFailed("Test case failed", "Expected " + expectedValue
                    + " but actually got " + actualValue);
        }
        return true;
    }


    public void debug(String msg) {
        tr.log(msg);
    }

    public void markFailed(String message1, String message2) {
        message1 = "Plan " + lastImportedPlan + " : " + message1;
        tr.markFailed(message1, message2);
    }

    public void markPassed(String message1) {
        message1 = "Plan " + lastImportedPlan + " : " + message1;
        tr.markPassed(message1);
    }


    /**
    * pass in a file name, or a relative path to a file from the
    * test directory, and it will import the file.
    */
    public Plan importXPDL(String localFileName)
        throws Exception
    {
        return createPlanFromXPDL(tr.getTestDir()+localFileName);
    }


    /**
     * This method creates the template from the XPDL
     */
    public Plan createPlanFromXPDL(String fileName) throws Exception
    {
        FileInputStream fis = null;
        File theFile = new File(fileName);
        if (!theFile.exists()) {
            // make a second try at CONFIGDIR
            File newFile = new File(tr.getTestDir(), fileName);
            if (newFile.exists()) {
                theFile = newFile;
            } else {
                throw new Exception(
                        "Failed createPlanFromXPDL.  Can't find the file ("
                                + theFile.getCanonicalPath()
                                + ") or the file ("
                                + newFile.getCanonicalPath() + ")");
            }
        }
        try {
            Plan plan = WFObjectFactory.getPlan();
            plan.setWFSession(wfSession);

            fis = new FileInputStream(theFile);
            plan.convertFromXPDL(fis);
            Plan newPlan = plan.createProcessDef();
            newPlan.startEdit();
            fixUpPlan(newPlan);
            newPlan.commitEdit();
            tr.log("Plan ("+newPlan.getId()+") created from "+ fileName);
            lastImportedPlan = plan.getName();
            return newPlan;
        } catch (Exception e) {
            throw new ExceptionWrapper("Failed createPlanFromXPDL("
                    + theFile.getCanonicalPath()
                    + ") - Possibly because of Plan.convertFromXPDL()", e);
        } finally {
            if (fis != null) {
                fis.close();
            }
        }
    }

    /**
     * This method creates the Process for the given template.
     */

    public ProcessInstance createProcess(Plan plan) throws Exception {
        tr.log("createProcess()");
        ProcessInstance pi = null;
        try {
            pi = plan.createProcessInstance();
            pi.start();
            long piId = pi.getId();
            debug("Created process with id:" + piId);

            String msg = "SafeSleeping till process id " + piId + " goes to running state";
            SafeSleep sleeper = new SafeSleep(msg);
            while(pi.getState() != ProcessInstance.STATE_RUNNING)
            {
                debug("Process instance "+piId+" is not running yet, still waiting...");
                sleeper.nap();
            }

            // if the control variable is defined, then it will be used in
            // handleWorkItems to control the test driver. If that variable
            // is NOT defined, then we must wait here for all process timers
            // to expire.
            DataItem control = pi.getDataItem("__DriverControl");

            if (control == null) {
                TimerStatus[] tStatus = pi.getStatusOfTimers();
                for (int j = 0; j < tStatus.length; j++) {
                    waitForTimerToExpire(tStatus[j].getTimerExpirationTime());
                }
            }
            return pi;
        } catch (Exception e) {
            checkNegativeTestCase(pi);
            throw new ExceptionWrapper(
                    "createProcess() - Possibly because of Plan.createProcessInstance()",
                    e);
        }
    }

    public void waitForTimerToExpire(long expireTime) throws Exception {
        String msg = "Waiting 60 seconds for process timers to expire";
        debug(msg);
        SafeSleep sleeper = new SafeSleep(msg);
        while(expireTime >= getCurrentTimeMillis())
        {
            sleeper.nap();
        }
    }

    public long getCurrentTimeMillis() {
        return System.currentTimeMillis();
    }


    /**
     * Throws an exception is the specified user is not in the specified group.
     * Call this routine if you test requires a particular user in a particular
     * group.
     *
     * Please use this function at the top of your tests to make sure that
     * groups are set up correctly on the given server.  Tests that assume
     * that a particular group is set up a particular way will fail if the
     * configuration is not correct.  This function will give you a clear
     * error message up front, instead of later failing for an unknown reason.
     */
    public void assertUserInGroup(String user, String group)
        throws Exception
    {
        DirectoryServices ds = WFObjectFactory.getDirectoryServices(wfSession,
                username, password);
        String[] members = ds.getUserList(group);
        boolean found = false;
        for (int i = 0; i < members.length; i++) {
            if (members[i].equalsIgnoreCase(user)) {
                found = true;
            }
        }
        if (!found) {
            throw new Exception(
                    "Configuration Problem.  The group ("
                            + group
                            + ") must contain the user ("
                            + username
                            + ") for this test to work.  Please either change the user, or set up the group correctly");
        }
        else {
            tr.log("Yes, '"+user+"' is a member of '"+group+"'");
        }
    }

    public
    Object[]
    getProcessWorkitems(
        int filter,
        String piName, // pass null if filtering on piName is not needed
        long piId)     // pass -1 if filtering on piId is not needed
        throws Exception
    {
        Object[] workItemList = null;
        String transportType = tr.getRequiredProperty("TWFTransportType");
        if(transportType.equalsIgnoreCase("LE")) {
            workItemList = getLEWorkitemList(filter, piName, piId);
        }
        else {
            workItemList = getNonLEWorkitemList(filter, piName, piId);
        }
        return workItemList;
    }

    private
    Object[]
    getLEWorkitemList(
        int filter,
        String piName, // pass null if filtering on piName is not needed
        long piId)     // pass -1 if filtering on piId is not needed
        throws Exception
    {
        WFObjectList wfol = WFObjectFactory.getWFObjectList(wfSession);
        wfol.openList(filter);
        Object[] allWorkItemList = wfol.getAllObjects();

        Vector idFilteredList = new Vector();
        if(allWorkItemList != null) {
            for(int i=0; i<allWorkItemList.length; i++){
                WorkItem wi = (WorkItem)allWorkItemList[i];
                if (piId != -1 && piId != wi.getProcessInstanceId()) {
                    continue;
                }
                idFilteredList.add(wi);
            }
        }

        Vector nameFilteredList = new Vector();
        for(int i=0; i<idFilteredList.size(); i++){
            WorkItem wi = (WorkItem)idFilteredList.elementAt(i);
            if (piName != null && !piName.equals(wi.getProcessInstanceName())) {
                continue;
            }
            nameFilteredList.add(wi);
        }

        Object[] returnList = new Object[nameFilteredList.size()];
        nameFilteredList.copyInto(returnList);
        return returnList;
    }

    private
    Object[]
    getNonLEWorkitemList(
        int filter,
        String piName, // pass null if filtering on piName is not needed
        long piId)     // pass -1 if filtering on piId is not needed
        throws Exception
    {
        WFObjectList wfObjectList = WFObjectFactory.getWFObjectList(wfSession);
        if (piName != null) {
            wfObjectList.addFilter(WFObjectList.LISTFIELD_PROCESSINSTANCE_NAME,"=",piName);
        }
        if (piId != -1) {
            wfObjectList.addFilter(WFObjectList.LISTFIELD_PROCESSINSTANCE_ID,"=","'"+piId+"'");
        }
        wfObjectList.openBatchedList(filter);
        Object[] wiList = wfObjectList.getNextBatch(1000);
        Vector wiVector = new Vector();
        while (wiList != null) {
            for(int i = 0; i < wiList.length; i++) {
                wiVector.add(wiList[i]);
            }
            wiList = wfObjectList.getNextBatch(1000);
        }
        Object[] returnList = new Object[wiVector.size()];
        wiVector.copyInto(returnList);
        return returnList;
    }

    public
    WorkItem
    waitForWorkItem(
        String wiName,
        String userName,
        int filter,
        String piName, // pass null if filtering on piName is not needed
        long piId,     // pass -1 if filtering on piId is not needed
        TestRecorder tr)
        throws Exception
    {
        String msg = "SafeSleeping till workitem named '" + wiName + "' assigned to '" + userName +
                                              "' appears in " + getFilterName(filter) + " filter";
        debug(msg);
        SafeSleep sleeper = new SafeSleep(msg);
        while(true) {
            Object[] wiList = getProcessWorkitems(filter, piName, piId);
            for(int i = 0; i < wiList.length; i++) {
                WorkItem wi = (WorkItem) wiList[i];
                if (wi.getName().equals(wiName) && wi.getAssignee().equals(userName)) {
                    return wi;
                }
            }
            sleeper.nap();
        }
    }

    private
    String
    getFilterName(
        int filter)
    {
        switch (filter)
        {
            case Filter.MyWorkItems: return "MyWorkItems";
            case Filter.MyActiveWorkItems: return "MyActiveWorkItems";
            case Filter.MyAcceptedWorkItems: return "MyAcceptedWorkItems";
            case Filter.MyDeclinedWorkItems: return "MyDeclinedWorkItems";
            case Filter.MyCompletedWorkItems: return "MyCompletedWorkItems";
            case Filter.AllInactiveWorkItems: return "AllInactiveWorkItems";
            case Filter.AllWorkItems: return "AllWorkItems";
            case Filter.AllProcesses: return "AllProcesses";
            case Filter.AllActiveProcesses: return "AllActiveProcesses";
            case Filter.AllInactiveProcesses: return "AllInactiveProcesses";
            case Filter.AllProcessesInErrorState: return "AllProcessesInErrorState";
            case Filter.AllArchivedProcesses: return "AllArchivedProcesses";
            case Filter.MyProcesses: return "MyProcesses";
            case Filter.MyActiveProcesses: return "MyActiveProcesses";
            case Filter.MyInactiveProcesses: return "MyInactiveProcesses";
            case Filter.AllPlans: return "AllPlans";
            case Filter.AllArchivedPlans: return "AllArchivedPlans";
            case Filter.MyPlans: return "MyPlans";
            case Filter.MyInactivePlans: return "MyInactivePlans";
            default : return "undefined";
        }
    }


    public void unsafeSleep(int millis)
    {
        tr.log("Sleeping now for "+millis+" milliseconds.");
        tr.log("   This is unsafe sleep for a fixed large duration.");
        tr.log("   We are trying to get rid of all these uses.");
        tr.log("   This should be replaced with SafeSleep loop");
        try {
            Thread.sleep(millis);
        } catch (Exception ie) {
            tr.log("Exception caught while sleeping: "+ie);
        }
    }



}

