/*
 * $Header: /home/cvspublic/jakarta-slide/testsuite/testsuite/junit/src/org/apache/slide/testsuite/testtools/tprocessor/TProcessors.java,v 1.87 2005/01/10 18:26:40 luetzkendorf Exp $
 * $Revision: 1.87 $
 * $Date: 2005/01/10 18:26:40 $
 *
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Slide", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */

package org.apache.slide.testsuite.testtools.tprocessor;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Vector;

import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.util.URIUtil;

import org.apache.slide.testsuite.testtools.tutil.TArgs;
import org.apache.slide.testsuite.testtools.tutil.XConf;
import org.apache.webdav.lib.util.WebdavStatus;
import org.apache.webdav.lib.WebdavState;
import org.apache.webdav.lib.methods.*;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;


/**
 * Main class to performe a test case and check and report the results
 *
 * @version $Revision: 1.87 $
 */
public class TProcessors {
    
    
    
    /** Table for startup parameter */
    Hashtable startUp = null;
    
    
    /** home */
    File config;
    
    /** port */
    String port;
    
    /** host */
    String host;
    
    /** user */
    String defaultUser;
    
    /** password */
    String defaultPassword;
    
    
    /** url encoding */
    String defaultUrlEncoding;
    
    
    /** Tracing required */
    String tracingRequest;
    
    
    /** hold all the variable defined **/
    KnownVariablesHashtable knownVariables = new KnownVariablesHashtable();
    
    
    /** holds all configuration **/
    XConf xdavConfiguration = null;
    
    /** specify where to write the xml results **/
    private XMLOutput xmlresult = new XMLOutput(System.out);
    
    /** count all executed test elements **/
    private long testElementsExecuted = 0;
    
    /** count all executed test elements **/
    private long testElementsExecutedWithError = 0;
    
    /** count all skiped test elements */
    private long testElementsSkipped = 0;
    
    /** The absolute path name of the execute input file **/
    private String globalAbsolutePath = null;
    
    
    /** The absolute path name of the execute input file **/
    private String globalTestFileName = null;
    
    protected final Properties properties;
    
    /** Configures from System properties **/
    public static TProcessors create() throws IOException, JDOMException {
        String home;
        File config;
        
        home = System.getProperty("xdav.home");
        if (home == null) {
            throw new IOException("xdav.home is not set!");
        }
        config = new File(home + File.separator + "testsuite" + File.separator + "junit" + File.separator + "tprocessor.xml");
        return new TProcessors(config, System.getProperty("xdav.host"),
                               System.getProperty("xdav.port"), System.getProperty("xdav.user"),
                               System.getProperty("xdav.password"), System.getProperty("xdav.urlencoding"),
                               System.getProperty("xdav.tracingRequest",  "none"),
                               System.getProperties());
    }

    /**
     * For testing purposes only, creates a TProcessor from properties found 
     * in xdav.home/tp.properties (where xdav.home is a system property).
     */
    private static TProcessors createFromFileProperties() throws IOException, JDOMException {
        String home;
        File config;
        
        home = System.getProperty("xdav.home");
        if (home == null) {
            throw new IOException("xdav.home is not set!");
        }
        Properties properties = new Properties();
        properties.load(new FileInputStream(home + File.separator + "tp.properties"));
        
        // replace ${xdav.NAME} with %NAME% in property values to enable resolution
        // of test variables (which is done within ant otherwise)
        for(Enumeration e = properties.propertyNames(); e.hasMoreElements(); ) {
            String key = (String)e.nextElement();
            String value = properties.getProperty(key);
            value = replace(value, "${xdav.", "%");
            value = replace(value, "}", "%");
            properties.setProperty(key, value);
        }
        
        config = new File(home + File.separator + "testsuite" + 
                File.separator + "junit" + File.separator + "tprocessor.xml");
        return new TProcessors(config, 
                properties.getProperty("xdav.host"),
                properties.getProperty("xdav.port"), 
                properties.getProperty("xdav.user"),
                properties.getProperty("xdav.password"), 
                properties.getProperty("xdav.urlencoding"),
                properties.getProperty("xdav.tracingRequest",  "none"),
                properties);
    }

    
    /**
     * Constructor
     * 
     * @param config configuration file (see testsuite/testsuite/junit/tprocessor.xml)
     */
    
    public TProcessors (File config, String host, String port, String defaultUser,
                        String defaultPassword, String defaultUrlEncoding, String tracingRequest,
                        Properties properties)
        throws IOException, JDOMException {
        this.config = config;
        this.host = host;
        this.port = port;
        this.defaultUser = defaultUser;
        this.defaultPassword = defaultPassword;
        this.defaultUrlEncoding = defaultUrlEncoding;
        this.tracingRequest = tracingRequest;
        this.properties = properties;
        
        if (defaultUrlEncoding == null) {
            defaultUrlEncoding = "UTF-8";
        }
        if (defaultUser == null) {
            defaultUser = "guest";
        }
        if (defaultPassword == null) {
            defaultPassword = "guest";
        }
        if (host == null) {
            throw new IOException("xdav.host is not set!");
        }
        if (config == null) {
            throw new IOException("config file is not set!");
        }
        if (port == null) {
            throw new IOException("xdav.port is not set!");
        }
        
        // Set some defaults
        startUp = new Hashtable();
        startUp.put("host", host);
        startUp.put("port", new Integer(port));
        
        
        xdavConfiguration = new XConf(config);
    }
    
    
    
    /**
     * main method
     */
    public static void main(String[] args) {
        if (!mainExecuter(args, null, null)) {
            System.exit (-1);
        }
    }
    
    /**
     * main executer method to be called from outside
     */
    public static boolean mainExecuter(String[] args, java.io.PrintStream xmlresult, int[] counters) {
        TProcessors tp;
        String stestName = "";
        boolean result = true;
        TArgs arg = new TArgs (args);
        
        
        try {
            if (arg.isOptionSet("file")) {
                tp = TProcessors.createFromFileProperties();
            } else {
                tp = TProcessors.create();
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
            return false; // dummy
        }
        if(!tp.processStartUp( args )){
            System.exit(-1);
        }
        
        if (arg.isOptionSet ("name")) {
            stestName = arg.get ("name");
        }
        else {
            return false;
        }
        
        
        if (!tp.executeTestCaseVerbose( new File (stestName), xmlresult, System.out )) {
            result = false;
        }
        
        if (counters != null) {
            counters[0] ++;
            if (!result) counters[1] ++;
            //          System.out.println("$$$$$$$$$$$ processed cases     " + counters[0]);
            //          System.out.println("$$$$$$$$$$$ processed cases err " + counters[1]);
            //          System.out.println("");
        }
        
        
        
        return result;
    }
    
    
    
    
    
    
    /**
     * Process the initial args passed to the Class.<br>
     * Currently this should consist of:<br>
     *
     * -host [the host to connect to]</br>
     * -port [the port the host is running webdav on]<br>
     *
     * There are default set if anything is missing.<br>
     *
     * If all goes well, a session will be created and credentials set.
     */
    private boolean processStartUp( String[] args ){
        boolean ready = false;
        
        
        TArgs arg = new TArgs (args);
        
        if (arg.isOptionSet ("help")) {
            
            printStartUsage();
            return false;
        }
        if (arg.isOptionSet("host")){
            
            
            startUp.put("host", arg.get("host"));
            
            ready = true;
        }
        if(arg.isOptionSet("port")){
            int val;
            try {
                val = Integer.parseInt( arg.get("port") );
            }
            catch ( NumberFormatException ne ) {
                printStartUsage();
                return false;
            }
            startUp.put("port", new Integer(val) );
            
            
            ready = true;
        }
        
        
        if (arg.isOptionSet( "name" ) ){
            ready = true;
        }
        
        
        return ready;
    }
    /**
     * Print the commands options from startup
     */
    private static void printStartUsage() {
        
        System.out.println("XDAV Tprocessor Startup args:");
        System.out.println("  -name <The name of testcase, full path should be given>");
        System.out.println("  -host <host to connect to>");
        System.out.println("  -port <post to conect on>");
        System.out.println("  -help");
        
        
    }
    // -------------------------------------------------------- Private Methods
    
    public boolean executeTestCaseVerbose(File testfile, PrintStream xmlresultPar, PrintStream verbose) {
        boolean result;
        
        verbose.println("");
        verbose.println(new Date() + " Starting test case: " + testfile.getAbsolutePath());
        
        try {
            result = executeTestCase(testfile, xmlresultPar);
        } catch (IOException e) {
            e.printStackTrace(verbose);
            result = false;
        } catch (JDOMException e) {
            e.printStackTrace(verbose);
            result = false;
        }
        if (!result) {
            verbose.println(new Date() + " FAILED test case: " + testfile.getAbsolutePath());
        }
        return result;
    }
    
    /**
     * Construct a document (tree) out of given XML file. Execute testStep given in the
     * file
     * @param WeddavClient client, File
     */
    
    public boolean executeTestCase(File testfile, PrintStream xmlresultPar) throws IOException, JDOMException {
        long time = System.currentTimeMillis();
        
        if (xmlresultPar != null){
            this.xmlresult = new XMLOutput(xmlresultPar);
        }
        
        xmlresult.writeRootElementStart("testCase");
        xmlresult.writeElement("fileName", testfile.getAbsolutePath());
        
        Element testElement = new SAXBuilder(true).build(testfile).getRootElement();
        Element specElement = testElement.getChild("specification");
        boolean conditionsOk = true;
        if (specElement != null) {
            conditionsOk = conditionsAreFullfilled(
                    specElement.getChildren("condition").iterator());
        }
        
        if (conditionsOk) {
            try{
                globalTestFileName = testfile.getName();
                globalAbsolutePath = testfile.getParent();
                //          globalAbsolutePath = testfile.;
                executeStepOrRepeater(testElement.getChildren().iterator());
            } catch (JDOMException e){
                xmlresult.writeElement("domException", e.toString());
                throw e;
            }
            xmlresult.writeElement("result",  ((testElementsExecutedWithError!=0)?"ERROR":"Success"));
            xmlresult.writeElement("time",  ((System.currentTimeMillis() - time)));
            xmlresult.writeElement("testElementCount",  testElementsExecuted);
            xmlresult.writeElement("testElementErrors",  testElementsExecutedWithError);
            xmlresult.writeElement("testElementSkipped",  testElementsSkipped);
        } else {
            xmlresult.writeElement("result",  "SKIPPED");
        }
        xmlresult.writeRootElementEnd("testCase");
        xmlresult.flush();
        
        return testElementsExecutedWithError==0;
    }
    
    
    protected boolean executeStepOrRepeater(Iterator items) throws IOException, JDOMException {
        boolean result           = true;
        Vector  threadsToExecute = new Vector(10);
        Vector  cleanUpThreads   = new Vector(10);
        
        result = executeStepOrRepeater(items, threadsToExecute, cleanUpThreads);
        
        try {
            // now execute all collected threads in parallel
            for (int i = 0 ; i<threadsToExecute.size(); i++) {
                ((Thread)threadsToExecute.elementAt(i)).start();
            }
            for (int i = 0 ; i<threadsToExecute.size(); i++) {
                ((Thread)threadsToExecute.elementAt(i)).join();
            }
            
            // now do the clean up tasks
            // now execute all collected threads in parallel
            for (int i = 0 ; i<cleanUpThreads.size(); i++) {
                ((Thread)cleanUpThreads.elementAt(i)).start();
            }
            for (int i = 0 ; i<cleanUpThreads.size(); i++) {
                ((Thread)cleanUpThreads.elementAt(i)).join();
            }
        } catch (InterruptedException e) {
            throw new IllegalStateException("unexpected InterruptedException");
        }
        
        return result;
        
    }
    
    
    
    private boolean executeStepOrRepeater(Iterator items, Vector threadsToExecute, Vector cleanUpThreads)
        throws IOException, JDOMException {
        HttpClientExtended client = new HttpClientExtended(defaultUser, defaultPassword);
        
        return executeStepOrRepeater(items, threadsToExecute, cleanUpThreads, client);
    }
    
    
    private boolean checkRepeaterCondition(Element conditionContainer, String condition) throws IOException {
        if (condition == null) return false;
        String evaluatedCondition = replaceKnownVariable(conditionContainer, condition, "x=x");
        StringTokenizer expression = new StringTokenizer(evaluatedCondition, "=");
        if (expression.countTokens() != 2) throw new IOException("Condition malformed: " + evaluatedCondition);
        String leftSide = expression.nextToken();
        String rightSide = expression.nextToken();
        return leftSide.equals(rightSide);
    }
    
    
    
    private boolean executeStepOrRepeater(Iterator items, Vector threadsToExecute, Vector cleanUpThreads, HttpClientExtended client)
        throws IOException, JDOMException
    {
        boolean result        = true;
        
        while(items.hasNext()){
            Element item = (Element) items.next();
            //              System.out.println("**** Execute of Step Element:  "+ item.getName());
            if (item.getName().equals("step")){
                if(!executeStep(item, client)){
                    result = false;
                }
            } else if(item.getName().equals("wait")){
                try {
                    Thread.currentThread().sleep(new Long(item.getAttributeValue("time")).longValue());
                } catch (InterruptedException e) {
                    throw new IllegalStateException("unexpected InterruptedException");
                }
            } else if(item.getName().equals("thread")){
                threadsToExecute.add(new Thread(new Runner(item.getChildren().iterator(), Thread.currentThread()), "parallelThread-" + (threadsToExecute.size()+1)));
            } else if(item.getName().equals("cleanup")){
                cleanUpThreads.add(new Thread(new Runner(item.getChildren().iterator(), Thread.currentThread()), "cleanUpThread"));
            } else if(item.getName().equals("repeater")){
                String varName = "repeatCounter";
                if (item.getAttribute("varDefinition") != null &&
                    !item.getAttributeValue("varDefinition").equals("") ) {
                    varName = item.getAttributeValue("varDefinition");
                }
                String condition = null;
                if (item.getAttribute("condition") != null &&
                    !item.getAttributeValue("condition").equals("") ) {
                    condition = item.getAttributeValue("condition");
                }
                knownVariables.put(varName, "undefined");
                int stackMarker = knownVariables.size();
                long maximum = new Long(replaceKnownVariable(item, item.getAttribute("repeatCount").getValue(), "0")).longValue();
                for (long i = 0; i < maximum && !checkRepeaterCondition(item, condition); i++){
                    knownVariables.removeAllFramedVariables(stackMarker);
                    knownVariables.put(varName, new Long(i+1).toString());
                    result = executeStepOrRepeater(item.getChildren().iterator(), threadsToExecute, cleanUpThreads, client) && result;
                }
            } else if(item.getName().equals("specification")){
                // do nothing in case of docu
            } else {
                System.out.println("#########################");
                System.out.println("Illegal entry in XML = " + item.getName() );
                System.out.println("Received response code = " + getElementString(item) );
                System.out.println("#########################");
            }
        }
        
        return result;
    }
    
    
    private void assignVariables(List varDefinitions ) throws IOException {
        if (varDefinitions != null) {
            Iterator iter = varDefinitions.iterator();
            while (iter.hasNext()) {
                Element element = (Element)iter.next();
                if (element.getAttributeValue("varDefinition") != null)
                    knownVariables.put(element.getAttributeValue("varDefinition"), replaceKnownVariable(element));
            }
        }
    }
    
    
    
    
    
    /**
     * Execute a given request for a testStep
     * @param Element
     */
    
    private boolean executeStep(Element elt, HttpClientExtended client ) throws IOException, JDOMException {
        
        boolean result        = false;
        long    executionTime = 0;
        HttpMethod method     = null;
        
        xmlresult.writeElementStart("executeStep");
        
        boolean conditionsOk = conditionsAreFullfilled(
                elt.getChildren("condition").iterator());
        
        if (!conditionsOk) {
            xmlresult.writeElement("result", ("SKIPPED"));
            xmlresult.writeElement("time",  (executionTime));
            xmlresult.writeElementEnd("executeStep");
            this.testElementsExecuted++;
            this.testElementsSkipped++;
            return true;
        }
        

        // initialise user and password
        knownVariables.put("user", defaultUser);
        knownVariables.put("password", defaultPassword);
        
        assignVariables(elt.getChildren("assign"));
        
        String user       = replaceKnownVariable(elt.getChild("user"), defaultUser);
        String password   = replaceKnownVariable(elt.getChild("password"), defaultPassword);
        
        // set user and password to the computed values
        knownVariables.put("user", user);
        knownVariables.put("password", password);
        
        // create a new client, iff the user id has changed, else use the current HttpClient
        if (!client.getUser().equals(user)) {
            client = new HttpClientExtended(user, password);
        }
        
        
        //              System.out.println("############### user " + user);
        //              System.out.println("############### pwd  " + password);
        
        try{
            method = executeRequest(elt.getChild("request"));
            if (method == null) {
                System.out.println("#########################");
                System.out.println("Method pointer is null = " + getElementString(elt.getChild("request")) );
                System.out.println("#########################");
                return false;
            }
            xmlresult.writeElement("method", method.getName());
            xmlresult.writeElement("url", URIUtil.encodePath(method.getPath(),"UTF-8"));
            method.setRequestHeader("User-Agent", "Jakarta Slide TProcessor " + URIUtil.encodePath(globalTestFileName,"UTF-8"));
            
            final int MAX = 50;
            int i;
            for (i = 0; true; i++) {
                try {
                    long time           = System.currentTimeMillis();
                    client.executeMethod(method);
                    executionTime = (System.currentTimeMillis() - time);
                    break;
                }
                catch (java.net.ConnectException e) {
                    if (i == MAX) {
                        System.out.println("connect failed: ");
                        System.out.println("  name=" + method.getName());
                        System.out.println("  uri=" + method.getURI());
                        System.out.println("  user=" + user);
                        System.out.println("  password=" + password);
                        throw e;
                    } else {
                        if (i > 1) {
                            System.out.println(Thread.currentThread().getName() + ": retry connection " + i + " out of " + MAX);
                        }
                        try {
                            Thread.currentThread().sleep(i*i);
                        } catch( InterruptedException x ) {}
                        client = new HttpClientExtended(user, password); // the old client might be brocken
                        continue;
                    }
                }
            }
            //          System.out.println("Status Code "+ m.getStatusCode());
            //          System.out.println("Status Text "+ m.getStatusText());
            
            
            //          if (method instanceof PropFindMethod) {
            //              System.out.println("###################");
            //              System.out.println("###################");
            //              System.out.println("###################");
            //              if (((PropFindMethod)method).getResponseDocument() != null) System.out.println("body " + getElementString(((PropFindMethod)method).getResponseDocument().getDocumentElement()));
            //              System.out.println("###################");
            //              System.out.println("###################");
            //              System.out.println("###################");
            //          }
            
            
            fillVariables(method, elt.getChild("response"));
            
            result = (responseAssert(method, elt));
            
        } catch (JDOMException e) {
            xmlresult.writeException(e);
            throw e;
        } catch (IOException e) {
            xmlresult.writeException( e );
            throw e;
        } finally {
            if (method != null) method.releaseConnection();
        }
        
        if (!result) testElementsExecutedWithError ++;
        testElementsExecuted ++;
        
        
        xmlresult.writeElement("result", (!result?"ERROR":"Success"));
        xmlresult.writeElement("time",  (executionTime));
        xmlresult.writeElementEnd("executeStep");
        
        
        return result;
    }
    
    /**
     * Constructs from request the method associated to the given request
     * @param Element
     */
    
    private HttpMethod executeRequest(Element request) throws IOException {
        HttpMethod method = null;
        List list = request.getChildren();
        Iterator items = list.iterator();
        
        
        while(items.hasNext()){
            Element item = (Element) items.next();
            
            if(item.getName().equals("command")){
                method = fillMethodName(item, method);
            }
            else if(item.getName().equals("header")){
                method = fillHeader(item, method);
            } else if(item.getName().equals("body")){
                method = fillBody(item, method);
            }
            
        }
        return method;
    }
    
    
    /**
     * This method is used to replace variables in a element
     * @param element the element to replacement should be applied
     * @return String the value of the element, with all applied replacements, if element is null return null
     */
    private  String replaceKnownVariable(Element element) throws IOException {
        return replaceKnownVariable(element, null);
    }
    
    
    /**
     * This method checks if a name points to a valid file
     * @param fileName
     * @return true if the file name is valid
     */
    private  boolean checkFileName(String name){
        //      System.out.println("Checking " + name + " = " + new File(name).isFile());
        return new File(name).isFile();
    }
    
    
    
    /**
     * This method returns the name of a valid file
     * @param name is the possibly unqualified file name
     * @return return name, if the unqualified File exists, else qualify the name
     */
    private  String getFileReferenceName(Element name){
        String result = null;
        String fileName = name.getAttributeValue("fileReference");
        if (fileName != null && fileName.length() > 1) {
            result = getFileReferenceName(replaceKnownVariable(name, fileName, fileName));
        }
        return result;
    }
    
    
    /**
     * This method returns the name of a valid file
     * @param name is the possibly unqualified file name
     * @return return name, if the unqualified File exists, else qualify the name
     */
    private  String getFileReferenceName(String name){
        String result = name;
        if (globalAbsolutePath != null) {
            if (checkFileName(name)) {
                result = name;
            } else if (checkFileName(globalAbsolutePath+File.separatorChar+name)) {
                result = globalAbsolutePath+File.separatorChar+name;
            }
        }
        return result;
    }
    
    
    
    /**
     * This method returns the name of a valid file
     * @param name is the possibly unqualified file name
     * @return return name, if the unqualified File exists, else qualify the name
     */
    private  String getFileEncoding(Element name){
        String result = name.getAttributeValue("fileEncoding");
        if (result == null || result.equals("")) result = "UTF-8";
        return result;
    }
    
    
    /**
     * This method is used to replace variables in a string
     * @param element the element to replacement should be applied
     * @param defaultValue if element is null, return this default
     * @return String the value of the element, with all applied replacements, if element is null return defaultValue
     */
    private  String getBodyValue(Element body) throws IOException {
        String result = body.getText();
        String fileName = getFileReferenceName(body);
        if (fileName != null) {
            
            Reader input  = new BufferedReader(
                new InputStreamReader(
                                                  new FileInputStream(fileName), getFileEncoding(body)));
            StringWriter output = new StringWriter();
            char[] buffer = new char[1024];
            int numberOfBytesRead;
            
            while ((numberOfBytesRead=input.read(buffer)) > 0) {
                output.write (buffer, 0, numberOfBytesRead);
            }
            
            result = output.toString();
            input.close();
            output.close();
        }
        return result;
    }
    
    /**
     * This method is used to replace variables in a string
     * @param element the element to replacement should be applied
     * @param defaultValue if element is null, return this default
     * @return String the value of the element, with all applied replacements, if element is null return defaultValue
     */
    private  String replaceKnownVariable(Element element, String defaultValue) throws IOException {
        if (element == null) return defaultValue;
        return replaceKnownVariable(element, getBodyValue(element), defaultValue);
    }
    
    
    /**
     * This method is used to replace variables in a string. If a variable value uses other variables
     * those variables are subsituted too.
     * @param element the element containing variable definitions
     * @param line the input line the replacement should be applied
     * @param defaultValue if element is null, return this default
     * @return String the value of the element, with all applied replacements, if element is null return defaultValue
     */
    private  String replaceKnownVariable(Element element, String line, String defaultValue){
        
        if (element == null) return defaultValue;
        
        if (element.getAttributeValue("varUsage") != null && !element.getAttributeValue("varUsage").equals("")){
            
            if (element.getAttributeValue("varUsage").indexOf(",") == (-1)) {
                String varName = "varUsage";  // default
                line = replace(line, "%"+varName+"%", computeVarValue(element, element.getAttributeValue("varUsage")));
                varName = element.getAttributeValue("varUsage");  // real name
                line = replace(line, "%"+varName+"%", computeVarValue(element, varName));
            } else {
                StringTokenizer varNames = new StringTokenizer(element.getAttributeValue("varUsage"), ",");
                while (varNames.hasMoreElements()) {
                    String varName = (String)varNames.nextElement();
                    line = replace(line, "%"+varName+"%", computeVarValue(element, varName));
                }
            }
        }
        return line;
    }
    /**
     * This method is used to replace variables in a string. If a variable value uses other variables
     * those variables are NOT subsituted.
     * @param element the element containing variable definitions
     * @param line the input line the replacement should be applied
     * @param defaultValue if element is null, return this default
     * @return String the value of the element, with all applied replacements, if element is null return defaultValue
     */
    private  String doReplaceKnownVariable(Element element, String line, String defaultValue){
        
        if (element == null) return defaultValue;
        
        if (element.getAttributeValue("varUsage") != null && !element.getAttributeValue("varUsage").equals("")){
            
            if (element.getAttributeValue("varUsage").indexOf(",") == (-1)) {
                String varName = "varUsage";  // default
                line = replace(line, "%"+varName+"%", knownVariables.get(element.getAttributeValue("varUsage")));
                varName = element.getAttributeValue("varUsage");  // real name
                line = replace(line, "%"+varName+"%", knownVariables.get(varName));
            } else {
                StringTokenizer varNames = new StringTokenizer(element.getAttributeValue("varUsage"), ",");
                while (varNames.hasMoreElements()) {
                    String varName = (String)varNames.nextElement();
                    line = replace(line, "%"+varName+"%", knownVariables.get(varName));
                }
            }
        }
        return line;
    }
    
    
    
    /**
     * This method computes for a given variable name the value. If the value contains
     * additional variable references, these are resolved to their values
     * @param element the element containing variable definitions
     * @param varName the name of the variable
     * @return value of the variable
     */
    public String computeVarValue(Element element, String varName){
        String result = knownVariables.get(varName);
        if (result != null && result.indexOf("%") != -1 && result.indexOf("%", result.indexOf("%")+1) != -1) {
            result = doReplaceKnownVariable(element, result, result);
        }
        return result;
    }
    
    
    
    
    /**
     * This method is used to replace variables in a string
     * @param line the string which will be modified
     * @param pattern search string to be replaced
     * @param value value of the replace string
     * @return value of line, with all applied replacements
     */
    public static String replace(String line, String pattern, String value){
        
        while (line.indexOf(pattern) != -1){
            line = line.substring(0, line.indexOf(pattern)) +
                value +
                line.substring(line.indexOf(pattern) + pattern.length());
        }
        //      System.out.println("#### Replaced " + pattern + " with " + value + " --> " + line);
        return line;
    }
    
    
    
    
    /**
     * This method is used to find out the name of the Resquest Method
     * @param List
     */
    private  HttpMethod fillMethodName(Element methodElement, HttpMethod method)
        throws IOException {
        StringTokenizer st = new StringTokenizer(replaceKnownVariable(methodElement));
        method = createHttpMethod(st.nextToken(), st.nextToken());
        method.setRequestHeader("Content-Length", "0");
        return method;
    }
    
    
    
    
    /**
     * This method is used to set the Header in request Method
     * @param List
     */
    private  HttpMethod fillHeader (Element header, HttpMethod method) throws IOException {
        Header headerToSet = getResponseHeader(header);
        String name  = headerToSet.getName();
        String value = headerToSet.getValue();
        if (name.equalsIgnoreCase("destination")) {
            value = URIUtil.encodePath(value, "utf-8");
        }
        
        method.setRequestHeader(name, value);
        return method;
    }
    
    private  boolean binaryReadRequest(Element element) {
        String fileReference = getFileReferenceName(element);
        String fileEncoding = element.getAttributeValue("fileEncoding");
        boolean result = (fileReference != null) &&
            (fileEncoding == null || fileEncoding.equals("binary"));
        return result;
    }
    
    
    
    /**
     * This method is used to set the Header in request Method
     */
    private  HttpMethod fillBody (Element element, HttpMethod method) throws IOException {
        if (binaryReadRequest(element)) {
            File input = new File(getFileReferenceName(element));
            if (!input.exists()) throw new IOException("File " + getFileReferenceName(element) + " does not exist");
            method.setRequestHeader("Content-Length", ""+input.length());
            if (method instanceof HttpRequestBodyMethodBase) {
                ((HttpRequestBodyMethodBase)method).setRequestBody(input);
            } else if (method instanceof PutMethod) {
                ((PutMethod)method).setRequestBody(new FileInputStream(input));
            }
        }
        else {
            try {
                String source = replaceKnownVariable(element);
                byte[] input = source.getBytes(getFileEncoding(element));
                //                System.out.println("##### encoding " + getFileEncoding(element));
                //                System.out.println("##### length   " + input.length);
                //                System.out.println("##### data     " + source);
                method.setRequestHeader("Content-Length", ""+input.length);
                if (method instanceof HttpRequestBodyMethodBase) {
                    ((HttpRequestBodyMethodBase)method).setRequestBody(input);
                } else if (method instanceof PutMethod) {
                    ((PutMethod)method).setRequestBody(new ByteArrayInputStream(input));
                }
            }
            catch (UnsupportedEncodingException e) {
                throw new IOException(e.toString());
            }
        }
        return method;
        
    }
    
    
    
    
    
    
    
    
    /**
     * This method is used to return body as String
     * @param String
     */
    public boolean responseAssert (HttpMethod m, Element elt) throws IOException, JDOMException {
        boolean firstPart = false;
        boolean secondPart = false;
        Iterator header = responseHeader(elt.getChild("response"));
        
        
        List expectedResponseCodes = responseStatus(elt.getChild("response").getChildText("command"));
        
        if (expectedResponseCodes != null && !expectedResponseCodes.contains(new Integer(m.getStatusCode()))) {
            
            xmlresult.writeElementStart("responseError");
            xmlresult.writeElement("receivedValue", WebdavStatus.getStatusText(m.getStatusCode()) +
                                       "   " + m.getStatusCode());
            xmlresult.writeElement("expectedValue", getStatusText(expectedResponseCodes) +
                                       "   " + expectedResponseCodes);
            xmlresult.writeElementEnd("responseError");
            
            if (m instanceof XMLResponseMethodBase && m.getStatusCode()==207 ) {
                System.out.println("#################");
                System.out.println(getElementString(((XMLResponseMethodBase)m).getResponseDocument().getDocumentElement()));
                System.out.println("#################");
            }
            
            return false;
        }
        
        // a received response code of 404 will not compare the headers
        ResponseBodyAssert methodAssert = assertFactory(m, elt, expectedResponseCodes);
        secondPart = (m.getStatusCode() == 404 && expectedResponseCodes != null &&
                          expectedResponseCodes.size() != 1 &&
                          expectedResponseCodes.contains(new Integer(404)))
            ||
            responseHeaderAssert(m, elt, header);
        
        firstPart  = (methodAssert!=null)?methodAssert.assertResponseBody():true;
        
        
        return ( firstPart && secondPart);
    }
    
    
    
    
    /**
     * This method is used to set the Header in request Method
     * @param List
     */
    private  String getStatusText (List responseCodes){
        
        String result = "";
        
        Iterator iter = responseCodes.iterator();
        while (iter.hasNext()) {
            result = result + " " + WebdavStatus.getStatusText(((Integer)iter.next()).intValue());
        }
        
        return result;
        
    }
    
    
    /**
     * This method is used to set the Header in request Method
     * @param String
     */
    private  Header getResponseHeader (String headerString){
        
        Header result = new Header();
        String name;
        String value;
        StringTokenizer st = new StringTokenizer(headerString);
        int size = st.nextToken().length();
        name = headerString.substring(0,size-1);
        if (size >= headerString.length()) {
            value = "";
        } else {
            value = headerString.substring(size+1);
        }
        value = value.trim();
        
        result.setName(name);
        result.setValue(value);
        
        return result;
        
    }
    
    
    /**
     * This method is used to set the Header in request Method
     * @param Element
     */
    private  Header getResponseHeader (Element headerElement) throws IOException {
        return getResponseHeader(replaceKnownVariable(headerElement));
    }
    
    /**
     * This method is used to return body as String
     * @param String
     */
    private boolean  responseHeaderAssert(HttpMethod m, Element elt, Iterator header) {
        return verifyHeader(xdavConfiguration.getPropertyList ("excludedHeaderProperties", m.getName()), m, header);
    }
    
    
    
    
    
    /**
     * This method is used to check if all the given headers are returned by the WebDav method
     * @param String
     */
    private boolean  verifyHeader(List allowHeaders, HttpMethod m, Iterator header){
        boolean result = true;
        while (header.hasNext()){
            
            Header  headToMatch      = (Header) header.next();
            boolean oneHeaderMatched = allowHeaders.contains(headToMatch.getName()) ||  // head to be compared
                verifyOneHeader(headToMatch, m.getResponseHeader(headToMatch.getName()));                                        // compare it
            
            if (!oneHeaderMatched){
                if (result) {
                    xmlresult.writeElementStart("headerErrors");
                }
                if (m.getResponseHeader(headToMatch.getName()) == null){
                    xmlresult.writeElement("nonExistingHeader", "name", headToMatch.getName(), headToMatch.getValue().trim());
                }
                else{
                    xmlresult.writeElementStart("nonMatchingHeader",  "name", headToMatch.getName());
                    xmlresult.writeElement("expectedValue", headToMatch.getValue().trim());
                    xmlresult.writeElement("receivedValue", m.getResponseHeader(headToMatch.getName()).getValue().trim());
                    xmlresult.writeElementEnd("nonMatchingHeader");
                }
            }
            
            result = result && oneHeaderMatched;
            
        }
        if (!result) {
            xmlresult.writeElementEnd("headerErrors");
        }
        return result;
    }
    
    
    
    
    
    private boolean  verifyOneHeader(Header expected, Header received){
        boolean result = false;
        result = received != null &&
            (expected.getValue().equals("*") ||
                 compareOneHeader(expected, received));
        return result;
    }
    
    
    
    private boolean  compareOneHeader(Header expected, Header received){
        boolean result = false;
        if (expected.getName().equals("Allow") || expected.getName().equals("DAV")) {
            result = compareCommaSeperatedListHeader(expected, received);
        } else {
            result = received.getValue().equalsIgnoreCase(expected.getValue());
        }
        return result;
    }
    
    
    
    private boolean  compareCommaSeperatedListHeader(Header expected, Header received){
        boolean result = false;
        HashSet expList = tokeniseCommaList(expected.getValue());
        HashSet recList = tokeniseCommaList(received.getValue());
        if (!expList.contains("*")) {
            // no wildcard ==> lists must be identical
            result = expList.equals(recList);
        } else {
            // wildcard ==> expected must be completely contained in received
            expList.remove("*");
            result = recList.containsAll(expList);
        }
        return result;
    }
    
    private HashSet tokeniseCommaList(String value){
        HashSet result = new HashSet();
        StringTokenizer iter = new StringTokenizer(value, ",");
        while (iter.hasMoreElements()) {
            String element = ((String)iter.nextElement()).trim();
            result.add(element);
        }
        return result;
    }
    
    
    private Iterator responseHeader(Element response) throws IOException {
        ArrayList result = new ArrayList();
        Iterator iter = response.getChildren("header").iterator();
        while (iter.hasNext()){
            Element headerElement = (Element)iter.next();
            result.add(getResponseHeader(headerElement));
        }
        return result.iterator();
    }
    
    
    
    
    
    public static List responseStatus (String response){
        List result = new ArrayList();
        if (response.equals("*")) return null;
        StringTokenizer st = new StringTokenizer(response);
        st.nextToken(); // ignore the first 'http' element
        String resonseString = st.nextToken();
        if (resonseString.equals("*"))       return null;
        if (!resonseString.startsWith("("))  result.add(new Integer(resonseString));
        if (resonseString.startsWith("(")) {
            StringTokenizer responseCodes = new StringTokenizer(resonseString, ",");
            while (responseCodes.hasMoreElements()) {
                String element = (String)responseCodes.nextElement();
                if (element.startsWith("(")) element = element.substring(1, element.length());
                if (element.endsWith(")")) element = element.substring(0, element.length()-1);
                if (!element.startsWith(")"))  result.add(new Integer(element));
            }
        }
        return result;
    }
    
    
    
//
    
    
    /**
     *
     */
    public static String getElementString (org.w3c.dom.Element e) {
        return getElementString(new org.jdom.input.DOMBuilder().build(e));
    }
    
    
    /**
     *
     */
    public static String getElementString (Element e) {
        XMLOutputter out = new XMLOutputter(org.jdom.output.Format.getPrettyFormat());
        
        if (e == null) {
            return "null";
        } else {
            return out.outputString(e);
        }
    }
    
    /**
     * Extract from expected response it body
     */
    protected  Element getElementFromString(String  input) throws IOException, JDOMException {
        
        if (input == null) return null;
        input = input.trim();
        if (input.equals("")) return null;
        return (new SAXBuilder().build(new StringReader(input))).getRootElement();
    }
    
    
    
    /**
     *
     */
    private void fillVariables (HttpMethod m, Element responseElement) {
        fillSpecifiedCommandHeaderVariable(m, responseElement);
        if (m instanceof XMLResponseMethodBase){
            fillAutomatedVariables((XMLResponseMethodBase)m);
            fillSpecifiedVariable((XMLResponseMethodBase)m, responseElement.getChild("body"));
        }
    }
    
    
    
    
    /**
     *
     */
    private void fillSpecifiedCommandHeaderVariable (HttpMethod m, Element responseElement) {
        
        if (responseElement == null) return;  // no response, nothing to fill
        
        // fill the command variable, if present
        String varName = responseElement.getChild("command").getAttributeValue("varDefinition");
        if (varName != null && !varName.trim().equals("")) {
            String responseCode = new Integer(m.getStatusCode()).toString();
            knownVariables.put(varName, responseCode);
        }
        
        // fill the header variable, if present
        Iterator iter = responseElement.getChildren("header").iterator();
        while (iter.hasNext()) {
            Element headerElement = (Element)iter.next();
            varName = headerElement.getAttributeValue("varDefinition");
            if (varName != null && !varName.trim().equals("")) {
                String headerName = getResponseHeader(headerElement.getText()).getName();
                Header h = m.getResponseHeader(headerName);
                String varValue ;
                if (h != null) varValue = h.getValue();
                else           varValue = "header " + headerName + " not found";
                knownVariables.put(varName, varValue);
            }
        }
    }
    /**
     *
     */
    private void fillSpecifiedVariable (XMLResponseMethodBase m, Element body) {
        
        if (body == null) return;  // no body, nothing to fill
        
        String searchPattern = body.getAttributeValue("varPath");
        String varName = body.getAttributeValue("varDefinition");
        if ((searchPattern != null) && (varName != null) && m.getResponseDocument() != null){
            
            String varValue = "";
            Iterator iter = findElementList(getMethodElement(m), searchPattern).iterator();
            while (iter.hasNext()) {
                varValue = ((Element)iter.next()).getText();
            }
            
            knownVariables.put(varName, varValue); // store only the very last ocuurence
            
        }
    }
    
    /**
     *
     */
    private void fillAutomatedVariables (XMLResponseMethodBase m) {
        List possible = xdavConfiguration.getPropertyList("automatedVariables", m.getName());
        Iterator iter = possible.iterator();
        while (iter.hasNext()){
            String searchPattern = (String)iter.next();
            if (((XMLResponseMethodBase)m).getResponseDocument() != null){
                fillAutomatedVariables(findElementList(getMethodElement(m), searchPattern));
            }
        }
    }
    
    
    
    
    private void fillAutomatedVariables (List valueList) {
        Iterator iter = valueList.iterator();
        while (iter.hasNext()){
            Element e = ((Element)iter.next());
            String varName  = "automaticVariable" + (knownVariables.size()-2);
            String varValue = e.getText();
            knownVariables.put(varName, varValue);
            
        }
    }
    
    private boolean conditionsAreFullfilled(Iterator conditions) {
        boolean result = true;
        for (;conditions.hasNext();) {
            result &= checkCondition((Element)conditions.next());
        }
        return result;
    }
    private boolean checkCondition(Element condition) {
        String name = condition.getAttributeValue("name");
        String requiredValue = condition.getTextTrim();
        String givenValue = (String)properties.get("xdav.condition." + name);
        
        boolean ok = (givenValue != null && givenValue.equals(requiredValue));
        if (!ok) {
            xmlresult.writeElement("violated-condition",
                    name + "=" + requiredValue + " (" + givenValue + ")");
        }
        return ok;
    }
    
    private Element getMethodElement(XMLResponseMethodBase m) {
        return new org.jdom.input.DOMBuilder().build(m.getResponseDocument().getDocumentElement());
    }
    
    
    
    
    private List findElementList (Element rootElement, String key) {
        
        List result = new ArrayList();
        
        if (key == null || key.equals("")) return result;
        
        if (rootElement.getName().equals(key)) {
            Element e = rootElement;
            if (key.equals("href")) {
                e = (Element)e.clone();
                try {e.setText(URIUtil.decode(e.getText(), defaultUrlEncoding));} catch (Exception ex) { }
            }
            result.add(e);
            return result;
        }
        if (key.indexOf("/") == -1) return result;
        
        String head = key.substring(0, key.indexOf("/"));
        String tail = key.substring(key.indexOf("/")+1);
        String child = null;
        
        //      System.out.println("@@@@@@@@@ xml   "  + rootElement.getName());
        
        if (!rootElement.getName().equals(head)) {
            return result;
        }
        
        if (tail.indexOf("/") == (-1)){
            child = tail;
        }
        else{
            child = tail.substring(0, tail.indexOf("/"));
        }
        
        //      System.out.println("@@@@@@@@@ head  "  + head);
        //      System.out.println("@@@@@@@@@ child "  + child);
        //      System.out.println("@@@@@@@@@ tail  "  + tail);
        
        
        Iterator iter = rootElement.getChildren().iterator();
        while (iter.hasNext()){
            Element e = (Element)iter.next();
            if (e.getName().equals(child))
                result.addAll(findElementList(e, tail));
        }
        return result;
    }
    
    
    private Element expectedResponseAsDOM(Element expectedResponse) throws IOException, JDOMException {
        return getElementFromString(expectedResponseAsString(expectedResponse));
    }
    
    
    private InputStream expectedResponseAsStream(Element element) throws IOException {
        
        Element expectedResponse = element.getChild("response").getChild("body");
        if (expectedResponse == null) return null;
        
        
        InputStream result = null;
        if (binaryReadRequest(expectedResponse)) {
            result = new FileInputStream(getFileReferenceName(expectedResponse));
        }
        else {
            result = new ByteArrayInputStream(
                replaceKnownVariable(expectedResponse).getBytes(
                                                 getFileEncoding(element)));
        }
        return result;
    }
    
    
    
    private String expectedResponseAsString(Element expectedResponse) throws IOException {
        if (expectedResponse.getChild("response").getChild("body") != null) {
            return replaceKnownVariable(expectedResponse.getChild("response").getChild("body"));
        }
        return null;
    }
    
    
    
    private ResponseBodyAssert assertFactory(HttpMethod m, Element expectedResponseElement, List expectedResponseCodes)
        throws IOException, JDOMException {
        
        // check for the wildcard
        String expectedResponseString = expectedResponseAsString(expectedResponseElement);
        if (expectedResponseString == null || expectedResponseString.equals("*")) return null;
        
        ResponseBodyAssert result = null;
        if(m instanceof PropFindMethod){
            result = new PropfindAssert((XMLResponseMethodBase) m, expectedResponseAsDOM(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
        } else if (m instanceof SearchMethod){
            boolean sortingRequired = true;
            
            if (expectedResponseElement.getChild("response").getChild("body") != null &&
                expectedResponseElement.getChild("response").getChild("body").getAttributeValue("nosorting") != null &&
                expectedResponseElement.getChild("response").getChild("body").getAttributeValue("nosorting").equalsIgnoreCase("true")) {
                sortingRequired = false;
            }
            
            result = new SearchAssert((XMLResponseMethodBase) m, expectedResponseAsDOM(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes, sortingRequired);
        } else if (m instanceof LockMethod){
            result = new LockAssert((XMLResponseMethodBase) m, expectedResponseAsDOM(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
        } else if (m instanceof PropPatchMethod){
            result = new PropPatchAssert((XMLResponseMethodBase) m, expectedResponseAsDOM(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
        } else if (m instanceof DeleteMethod){
            result = new DeleteAssert ((XMLResponseMethodBase) m, expectedResponseAsDOM(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
        } else if (m instanceof MoveMethod){
            result = new MoveAssert ((XMLResponseMethodBase) m, expectedResponseAsDOM(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
        } else if (m instanceof CopyMethod){
            result = new CopyAssert ((XMLResponseMethodBase) m, expectedResponseAsDOM(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
        } else if (m instanceof AclMethod){
            result = new AclAssert ((XMLResponseMethodBase) m, expectedResponseAsDOM(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
        } else if (m instanceof CheckinMethod){
            result = new CheckinAssert ((XMLResponseMethodBase) m, expectedResponseAsDOM(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
        } else if (m instanceof CheckoutMethod){
            result = new CheckoutAssert ((XMLResponseMethodBase) m, expectedResponseAsDOM(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
        } else if (m instanceof MkWorkspaceMethod){
            result = new MkWorkspaceAssert ((XMLResponseMethodBase) m, expectedResponseAsDOM(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
        } else if (m instanceof LabelMethod){
            result = new LabelAssert ((XMLResponseMethodBase) m, expectedResponseAsDOM(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
        } else if (m instanceof ReportMethod){
            result = new ReportAssert ((XMLResponseMethodBase) m, expectedResponseAsDOM(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
        } else if (m instanceof UncheckoutMethod){
            result = new UncheckoutAssert ((XMLResponseMethodBase) m, expectedResponseAsDOM(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
        } else if (m instanceof VersionControlMethod){
            result = new VersionControlAssert ((XMLResponseMethodBase) m, expectedResponseAsDOM(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
        } else if (m instanceof UpdateMethod){
            result = new UpdateAssert ((XMLResponseMethodBase) m, expectedResponseAsDOM(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
        } else if (m instanceof GetMethod) {
            if (m.getResponseHeader("Content-Type") == null || !m.getResponseHeader("Content-Type").getValue().trim().startsWith("text/xml")) {
                result = new GetAssert ( m, expectedResponseAsStream(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
            } else {
                result = new GetXMLAssert ( m, expectedResponseAsStream(expectedResponseElement), xdavConfiguration, xmlresult, expectedResponseCodes);
            }
        } else if (m instanceof PollMethod) {
           result = new PollAssert((XMLResponseMethodBase) m, 
                 expectedResponseAsDOM(expectedResponseElement), 
                 xdavConfiguration, xmlresult, expectedResponseCodes);
        }
        return result;
    }
    
    
    
    /**
     * Factory method to return a method object based on the methodName.
     * If the method name is not known, null is returned
     */
    public static HttpMethod createHttpMethod(String methodName) throws IOException {
        HttpMethod result = null;
        if (methodName.equalsIgnoreCase("COPY")){
            result = new CopyMethod();
        }
        else if(methodName.equalsIgnoreCase("BIND")) {
            result = new BindMethod();
        }
        else if(methodName.equalsIgnoreCase("REBIND")) {
            result = new RebindMethod();
        }
        else if(methodName.equalsIgnoreCase("UNBIND")) {
            result = new UnbindMethod();
        }
        else if(methodName.equalsIgnoreCase("DELETE")){
            result = new DeleteMethod();
        }
        else if(methodName.equalsIgnoreCase("GET")){
            result = new GetMethod();
        }
        else if(methodName.equalsIgnoreCase("HEAD")){
            result = new HeadMethod();
        }
        else if(methodName.equalsIgnoreCase("LOCK")){
            result = new LockMethod();
        }
        else if(methodName.equalsIgnoreCase("MKCOL")){
            result = new MkcolMethod();
        }
        else if(methodName.equalsIgnoreCase("MOVE")){
            result = new MoveMethod();
        }
        else if(methodName.equalsIgnoreCase("LABEL")){
            result = new LabelMethod();
        }
        else if(methodName.equalsIgnoreCase("OPTIONS")){
            result = new OptionsMethod();
        }
        else if(methodName.equalsIgnoreCase("POST")){
            result = new PostMethod();
        }
        else if(methodName.equalsIgnoreCase("PROPFIND")){
            result = new PropFindMethod();
        }
        else if(methodName.equalsIgnoreCase("SEARCH")){
            result = new SearchMethod();
        }
        else if(methodName.equalsIgnoreCase("PROPPATCH")){
            result = new PropPatchMethod();
        }
        else if(methodName.equalsIgnoreCase("PUT")){
            result = new PutMethod();
        }
        else if(methodName.equalsIgnoreCase("POST")){
            result = new PostMethod();
        }
        else if(methodName.equalsIgnoreCase("UNLOCK")){
            result = new UnlockMethod();
        }
        else if(methodName.equalsIgnoreCase("ACL")){
            result = new AclMethod();
        }
        else if(methodName.equalsIgnoreCase("CHECKIN")){
            result = new CheckinMethod();
        }
        else if(methodName.equalsIgnoreCase("CHECKOUT")){
            result = new CheckoutMethod();
        }
        else if(methodName.equalsIgnoreCase("MKWORKSPACE")){
            result = new MkWorkspaceMethod();
        }
        else if(methodName.equalsIgnoreCase("REPORT")){
            result = new ReportMethod();
        }
        else if(methodName.equalsIgnoreCase("UNCHECKOUT")){
            result = new UncheckoutMethod();
        }
        else if(methodName.equalsIgnoreCase("VERSION-CONTROL")){
            result = new VersionControlMethod();
        }
        else if(methodName.equalsIgnoreCase("UPDATE")){
            result = new UpdateMethod();
        }
        else if(methodName.equalsIgnoreCase("SUBSCRIBE")){
            result = new SubscribeMethod();
        }
        else if(methodName.equalsIgnoreCase("UNSUBSCRIBE")){
           result = new UnsubscribeMethod();
        }
        else if(methodName.equalsIgnoreCase("POLL")){
           result = new PollMethod();
        }
        if (result == null)
            throw new IOException("Method " + methodName + " does not exist");
        result.setFollowRedirects(true);
        return result;
    }
    
    
    
    
    /**
     * Factory method to return a method object based on the methodName.
     * If the method name is not known, null is returned
     */
    public static HttpMethod createHttpMethod(String methodName, String path) throws IOException {
        HttpMethod result = createHttpMethod(methodName);
        if (result != null){
            result.setPath(URIUtil.encodePath(path, "utf-8"));
        }
        return result;
    }
    
    
    
    
    
    /**************************************************************/
    /* Inner class to help the thread to execute the function     */
    /**************************************************************/
    public class Runner implements Runnable{
        Iterator       items;
        String         name;
        HashtableStack varsToBeUsed;
        public Runner(Iterator items, Thread varsToBeUsed) {
            this.items = items;
            this.name = varsToBeUsed.getName();
            this.varsToBeUsed = new HashtableStack(knownVariables.getEnvironment(varsToBeUsed));
        }
        public void run() {
            knownVariables.createNewEnvironment(varsToBeUsed, name);
            try {
                executeStepOrRepeater(items);
            }
            catch (JDOMException e) {
                e.printStackTrace();
            }
            catch (IOException e) {
                e.printStackTrace();
            };
            knownVariables.removeEnvironment();
        }
    }
    
    
    
    
    
    /**************************************************************/
    /* Inner class to contain all variable sorted by thread name  */
    /**************************************************************/
    private class KnownVariablesHashtable {
        private Hashtable knownVarsByThread = new Hashtable();
        public String get(String key) {
            String    result = "";
            
            if (key.startsWith("globalVariable") ||
                key.equals("host")||
                key.equals("port")||
                key.equals("workdb")||
                key.equals("workhost") ) {
                return properties.getProperty("xdav." + key, "");
            }
            else {
                HashtableStack vars   = (HashtableStack)knownVarsByThread.get(Thread.currentThread().getName());
                if (vars != null){
                    result = (String)vars.get(key);
                }
            }
            if (tracingRequest.indexOf("var") != (-1)) System.out.println("***VAR reading " + key + " = " + result + "  in Thread " + Thread.currentThread().getName());
            return result;
        }
        public void put(String key, String value) {
            getEnvironment().put(key, value);
            if (tracingRequest.indexOf("var") != (-1)) System.out.println("***VAR setting " + key + " = " + value + "  in Thread " + Thread.currentThread().getName());
        }
        public int size() {
            return getEnvironment().size()+1;
        }
        public void removeAllFramedVariables(int stackMarker) {
            if (size() <= stackMarker) return;
            int max = size();
            for (int i = stackMarker; i <= max; i++)    {
                getEnvironment().pop();
            }
        }
        public HashtableStack createNewEnvironment(HashtableStack varsToBeUsed, String name) {
            HashtableStack vars;
            if (varsToBeUsed != null) {
                vars = new HashtableStack(varsToBeUsed);
            } else {
                vars = new HashtableStack();
            }
            knownVarsByThread.put(Thread.currentThread().getName(), vars);
            if (tracingRequest.indexOf("var") != (-1)) System.out.println("***********Frame("+knownVarsByThread.size()+"): created new Variable frame for " + Thread.currentThread().getName() + " based on  " + name);
            return vars;
        }
        public void removeEnvironment() {
            knownVarsByThread.remove(Thread.currentThread().getName());
            if (tracingRequest.indexOf("var") != (-1)) System.out.println("***********Frame("+knownVarsByThread.size()+"): removed Variable frame for " + Thread.currentThread().getName() );
        }
        public HashtableStack getEnvironment(Thread scope) {
            HashtableStack vars  = (HashtableStack)knownVarsByThread.get(scope.getName());
            if (vars == null){
                vars = createNewEnvironment(null, scope.getName());
            }
            return vars;
        }
        private HashtableStack getEnvironment() {
            return getEnvironment(Thread.currentThread());
        }
    }
    
    
    
    /**************************************************************/
    /* Inner class to have a HashMap act as a stack too           */
    /**************************************************************/
    private class HashtableStack extends Hashtable {
        private Stack stack = new Stack();
        public HashtableStack() {
            super();
        }
        public HashtableStack(Hashtable initValues) {
            super.putAll(initValues);
        }
        public Object put(Object key, Object value) {
            if (super.get(key) == null) {
                stack.push(key);
            }
            return super.put(key, value);
        }
        public Object pop() {
            if (tracingRequest.indexOf("var") != (-1)) System.out.println("***VAR removing " + stack.peek() + " = " + super.get(stack.peek()) + "  in Thread " + Thread.currentThread());
            return super.remove(stack.pop());
        }
    }
    
    
    
    
    /**************************************************************/
    /* Inner class to have some more function on HttpClient       */
    /**************************************************************/
    private class HttpClientExtended extends HttpClient {
        private String user = null;
        
        public void setUser(String user) {
            this.user = user;
        }
        
        public String getUser() {
            return user;
        }
        
        private HttpClientExtended (String user, String password){
            super();
            //            System.out.println("####### " + Thread.currentThread().getName() + " : Creating client " + user);
            setUser(user);
            WebdavState state = new WebdavState();
            Credentials cred = new UsernamePasswordCredentials(user, password);
            //        state.setURLDecodingCharset(defaultUrlEncoding);
            //        state.setURLEncodingCharset(defaultUrlEncoding);
//            startSession((String)startUp.get("host"),
//                             ((Integer)startUp.get("port") ).intValue());
            getHostConfiguration().setHost((String)startUp.get("host"),
                    ((Integer)startUp.get("port") ).intValue());
            state.setCredentials(null, (String)startUp.get("host"), cred);
            state.setAuthenticationPreemptive(true); // avoid non-authenticated method invocation
            setState(state);
        }
    }
}


