org.apache.cactus.internal.server.AbstractWebTestCaller.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.cactus.internal.server.AbstractWebTestCaller.java

Source

/* 
 * ========================================================================
 * 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * ========================================================================
 */
package org.apache.cactus.internal.server;

import junit.framework.Test;
import junit.framework.TestCase;
import org.apache.cactus.internal.CactusTestCase;
import org.apache.cactus.internal.HttpServiceDefinition;
import org.apache.cactus.internal.ServiceEnumeration;
import org.apache.cactus.internal.WebTestResult;
import org.apache.cactus.internal.configuration.Version;
import org.apache.cactus.internal.util.ClassLoaderUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.servlet.ServletException;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Constructor;

/**
 * Responsible for instanciating the <code>TestCase</code> class on the server
 * side, set up the implicit objects and call the test method. This class
 * provides a common abstraction for all test web requests.
 *
 * @version $Id: AbstractWebTestCaller.java 238991 2004-05-22 11:34:50Z vmassol $
 */
public abstract class AbstractWebTestCaller {
    /**
     * Name of the attribute in the <code>application</code> scope that will
     * hold the results of the test.
     */
    protected static final String TEST_RESULTS = "ServletTestRedirector_TestResults";

    /**
     * The logger.
     */
    private static final Log LOGGER = LogFactory.getLog(AbstractWebTestCaller.class);

    /**
     * The implicit objects (which will be used to set the test case fields
     * in the <code>setTesCaseFields</code> method.
     */
    protected WebImplicitObjects webImplicitObjects;

    /**
     * @param theObjects the implicit objects coming from the redirector
     */
    public AbstractWebTestCaller(WebImplicitObjects theObjects) {
        this.webImplicitObjects = theObjects;
    }

    /**
     * Sets the implicit object in the test case class.
     *
     * @param theTestCase the instance of the test case class on which the
     *        class variable (implicit objects) should be set
     * @exception Exception if an errors occurs when setting the implicit
     *            objects
     */
    protected abstract void setTestCaseFields(TestCase theTestCase) throws Exception;

    /**
     * @return a <code>Writer</code> object that will be used to return the
     *         test result to the client side.
     * @exception IOException if an error occurs when retrieving the writer
     */
    protected abstract Writer getResponseWriter() throws IOException;

    /**
     * Calls a test method. The parameters needed to call this method are found
     * in the HTTP request. Save the results in the <code>application</code>
     * scope so that the Get Test Result service can find them.
     *
     * @exception ServletException if an unexpected error occurred
     */
    public void doTest() throws ServletException {
        WebTestResult result = null;

        try {
            // Create an instance of the test class
            TestCase testInstance = getTestClassInstance(getTestClassName(), getWrappedTestClassName(),
                    getTestMethodName());

            // Set its fields (implicit objects)
            setTestCaseFields(testInstance);

            // Call it's method corresponding to the current test case
            if (testInstance instanceof CactusTestCase) {
                ((CactusTestCase) testInstance).runBareServer();

            } else {
                testInstance.runBare();
            }

            // Return an instance of <code>WebTestResult</code> with a
            // positive result.
            result = new WebTestResult();
        } catch (Throwable e) {
            // An error occurred, return an instance of
            // <code>WebTestResult</code> with an exception.
            result = new WebTestResult(e);
        }

        LOGGER.debug("Test result : [" + result + "]");

        // Set the test result.
        this.webImplicitObjects.getServletContext().setAttribute(TEST_RESULTS, result);

        LOGGER.debug("Result saved in context scope");
    }

    /**
     * Return the last test results in the HTTP response.
     *
     * @exception ServletException if an unexpected error occurred
     */
    public void doGetResults() throws ServletException {
        // One could think there is a potential risk that the client side of
        // Cactus will request the result before it has been written to the
        // context scope as the HTTP request will not block in some containers.
        // However this will not happen because on the client side, once the
        // first request is done to execute the test, all the result is read
        // by the AutoReadHttpURLConnection class, thus ensuring that the
        // request is fully finished and the result has been committed ...
        WebTestResult result = (WebTestResult) (this.webImplicitObjects.getServletContext()
                .getAttribute(TEST_RESULTS));

        // It can happen that the result has not been written in the Servlet
        // context. This could happen for example when using a load-balancer
        // which would direct the second Cactus HTTP connection to another
        // instance. In that case, we throw an error.
        if (result == null) {
            String message = "Error getting test result. This could happen "
                    + "for example if you're using a load-balancer. Please disable "
                    + "it before running Cactus tests.";

            LOGGER.error(message);
            throw new ServletException(message);
        }

        LOGGER.debug("Test Result = [" + result + "]");

        // Write back the results to the outgoing stream as an XML string.

        // Use UTF-8 to transfer the result back
        webImplicitObjects.getHttpServletResponse().setContentType("text/xml; charset=UTF-8");

        try {
            Writer writer = getResponseWriter();

            writer.write(result.toXml());
            writer.close();
        } catch (IOException e) {
            String message = "Error writing WebTestResult instance to output " + "stream";

            LOGGER.error(message, e);
            throw new ServletException(message, e);
        }
    }

    /**
     * Run the connection test between client and server. This is just to
     * ensure that configuration is set up correctly.
     *
     * @exception ServletException if an unexpected error occurred
     */
    public void doRunTest() throws ServletException {
        // Do not return any http response (not needed). It is enough to
        // know this point has been reached ... it means the connection has
        // been established !
    }

    /**
     * Return the cactus version. This is to make sure both the client side
     * and server side are using the same version.
     *  
     * @exception ServletException if an unexpected error occurred
     */
    public void doGetVersion() throws ServletException {
        try {
            Writer writer = getResponseWriter();
            writer.write(Version.VERSION);
            writer.close();
        } catch (IOException e) {
            String message = "Error writing HTTP response back to client " + "for service ["
                    + ServiceEnumeration.GET_VERSION_SERVICE + "]";

            LOGGER.error(message, e);
            throw new ServletException(message, e);
        }
    }

    /**
     * Create an HTTP Session and returns the response that contains the
     * HTTP session as a cookie (unless URL rewriting is used in which
     * case the jsesssionid cookie is not returned).
     * 
     * @exception ServletException if an unexpected error occurred
     */
    public void doCreateSession() throws ServletException {
        // Create an HTTP session
        this.webImplicitObjects.getHttpServletRequest().getSession(true);

        try {
            Writer writer = getResponseWriter();
            writer.close();
        } catch (IOException e) {
            String message = "Error writing HTTP response back to client " + "for service ["
                    + ServiceEnumeration.CREATE_SESSION_SERVICE + "]";

            LOGGER.error(message, e);
            throw new ServletException(message, e);
        }
    }

    /**
     * @return the class to test class name, extracted from the HTTP request
     * @exception ServletException if the class name of the test case is missing
     *            from the HTTP request
     */
    protected String getTestClassName() throws ServletException {
        String queryString = this.webImplicitObjects.getHttpServletRequest().getQueryString();
        String className = ServletUtil.getQueryStringParameter(queryString, HttpServiceDefinition.CLASS_NAME_PARAM);

        if (className == null) {
            String message = "Missing class name parameter [" + HttpServiceDefinition.CLASS_NAME_PARAM
                    + "] in HTTP request.";

            LOGGER.error(message);
            throw new ServletException(message);
        }

        LOGGER.debug("Class to call = [" + className + "]");

        return className;
    }

    /**
     * @return the optional test class that is wrapped by a Cactus test case, 
     *         extracted from the HTTP request
     * @exception ServletException if the wrapped class name is missing from 
     *            the HTTP request
     */
    protected String getWrappedTestClassName() throws ServletException {
        String queryString = this.webImplicitObjects.getHttpServletRequest().getQueryString();
        String className = ServletUtil.getQueryStringParameter(queryString,
                HttpServiceDefinition.WRAPPED_CLASS_NAME_PARAM);

        if (className == null) {
            LOGGER.debug("No wrapped test class");
        } else {
            LOGGER.debug("Wrapped test class = [" + className + "]");
        }

        return className;
    }

    /**
     * @return the class method to call for the current test case, extracted
     *         from the HTTP request
     * @exception ServletException if the method name of the test case is
     *            missing from the HTTP request
     */
    protected String getTestMethodName() throws ServletException {
        String queryString = this.webImplicitObjects.getHttpServletRequest().getQueryString();
        String methodName = ServletUtil.getQueryStringParameter(queryString,
                HttpServiceDefinition.METHOD_NAME_PARAM);

        if (methodName == null) {
            String message = "Missing method name parameter [" + HttpServiceDefinition.METHOD_NAME_PARAM
                    + "] in HTTP request.";

            LOGGER.error(message);
            throw new ServletException(message);
        }

        LOGGER.debug("Method to call = " + methodName);

        return methodName;
    }

    /**
     * @return true if the auto session flag for the Session can be found in
     *         the HTTP request
     */
    protected boolean isAutoSession() {
        String queryString = this.webImplicitObjects.getHttpServletRequest().getQueryString();
        String autoSession = ServletUtil.getQueryStringParameter(queryString,
                HttpServiceDefinition.AUTOSESSION_NAME_PARAM);

        boolean isAutomaticSession = Boolean.valueOf(autoSession).booleanValue();

        LOGGER.debug("Auto session is " + isAutomaticSession);

        return isAutomaticSession;
    }

    /**
     * @param theClassName the name of the test class
     * @param theWrappedClassName the name of the wrapped test class. Can be
     *        null if there is none
     * @param theTestCaseName the name of the current test case
     * @return an instance of the test class to call
     * @exception ServletException if the test case instance for the current
     *            test fails to be instanciated (for example if some
     *            information is missing from the HTTP request)
     */
    protected TestCase getTestClassInstance(String theClassName, String theWrappedClassName, String theTestCaseName)
            throws ServletException {
        // Get the class to call and build an instance of it.
        Class testClass = getTestClassClass(theClassName);
        TestCase testInstance = null;
        Constructor constructor;

        try {
            if (theWrappedClassName == null) {
                constructor = getTestClassConstructor(testClass);

                if (constructor.getParameterTypes().length == 0) {
                    testInstance = (TestCase) constructor.newInstance(new Object[0]);
                    ((TestCase) testInstance).setName(theTestCaseName);
                } else {
                    testInstance = (TestCase) constructor.newInstance(new Object[] { theTestCaseName });
                }
            } else {
                Class wrappedTestClass = getTestClassClass(theWrappedClassName);
                Constructor wrappedConstructor = getTestClassConstructor(wrappedTestClass);

                TestCase wrappedTestInstance;
                if (wrappedConstructor.getParameterTypes().length == 0) {
                    wrappedTestInstance = (TestCase) wrappedConstructor.newInstance(new Object[0]);
                    wrappedTestInstance.setName(theTestCaseName);
                } else {
                    wrappedTestInstance = (TestCase) wrappedConstructor
                            .newInstance(new Object[] { theTestCaseName });
                }

                constructor = testClass.getConstructor(new Class[] { String.class, Test.class });

                testInstance = (TestCase) constructor
                        .newInstance(new Object[] { theTestCaseName, wrappedTestInstance });
            }
        } catch (Exception e) {
            String message = "Error instantiating class [" + theClassName + "([" + theTestCaseName + "], ["
                    + theWrappedClassName + "])]";

            LOGGER.error(message, e);
            throw new ServletException(message, e);
        }

        return testInstance;
    }

    /**
     * @param theTestClass the test class for which we want to find the
     *        constructor
     * @return the availble constructor for the test class
     * @throws NoSuchMethodException if no suitable constructor is found
     */
    private Constructor getTestClassConstructor(Class theTestClass) throws NoSuchMethodException {
        Constructor constructor;
        try {
            constructor = theTestClass.getConstructor(new Class[] { String.class });
        } catch (NoSuchMethodException e) {
            constructor = theTestClass.getConstructor(new Class[0]);
        }
        return constructor;
    }

    /**
     * @param theClassName the name of the test class
     * @return the class object the test class to call
     * @exception ServletException if the class of the current test case
     *            cannot be loaded in memory (i.e. it is not in the
     *            classpath)
     */
    protected Class getTestClassClass(String theClassName) throws ServletException {
        // Get the class to call and build an instance of it.
        Class testClass = null;

        try {
            testClass = ClassLoaderUtils.loadClass(theClassName, this.getClass());
        } catch (Exception e) {
            String message = "Error finding class [" + theClassName
                    + "] using both the Context classloader and the webapp "
                    + "classloader. Possible causes include:\r\n";

            message += ("\t- Your webapp does not include your test " + "classes,\r\n");
            message += ("\t- The cactus.jar is not located in your "
                    + "WEB-INF/lib directory and your Container has not set the "
                    + "Context classloader to point to the webapp one");

            LOGGER.error(message, e);
            throw new ServletException(message, e);
        }

        return testClass;
    }

}