package org.mockejb;

import java.lang.reflect.*;

import org.apache.cactus.*;

/**
 * Runs the test case on the server side under cactus or 
 * standalone on the client side. Concrete  test cases extend this
 * class. 
 * 
 * If "mockejb.cactus.mode" system property is set to true, the test case 
 * behaves as the Cactus' ServletTestCase, so it runs
 * on the server.  
 * If the property is false or not set, Cactus is ignored completely and the test
 * class runs on the client under JUnit.
 * Subclasses can override <code>isCactusEnabled()</code> to use a different
 * mechanism for turning cactus mode on and off.   
 * 
 * @author Alexander Ananiev
 */
public class OptionalCactusTestCase extends ServletTestCase {

    /**
     * Creates a new TestCase for the given test.
     * @param testName test name 
     */
    public OptionalCactusTestCase(String testName ) {
        super( testName );
    }
    
    
    /**
     * Tests if the test should run in Cactus mode. Cactus
     * mode is turned on if the system property "mockejb.cactus.mode" is set
     * to "true".
     * Subclasses can implement different way of specifying Cactus mode. 
     *  
     * @return true if "mockejb.cactus" system property set to true 
     */
    protected boolean isCactusMode(){
        String cactusProp = System.getProperty("mockejb.cactus.mode");
        
        return (cactusProp!=null && cactusProp.equalsIgnoreCase("true"));
         
    }
    
    /**
     * Tests if the test is being executed on the app server. 
     * @return true if the test runs on the app server
     */
    public boolean isRunningOnServer(){
        return ( this.request != null );
    }
    
    
    /**
     * Overrides the standard JUnit and Cactus method. 
     * This allows to 
     * turn the cactus off for this test class even though it continues 
     * to extend ServletTestCase.
     * The method first checks if the test runs on the client, in 
     * which case it calls <code>runBareLocally</code>/
     * If the test runs on the server, it calls "super" to 
     * delegate running the test to ServletTestCase
     * 
     * @throws Throwable in case of any problems in the test
     *       
     * @see junit.framework.TestCase#runBare()
     */
    public void runBare() throws Throwable {
        // if we are called on the client
        if ( ! isRunningOnServer() ) {
            runBareLocally();
        }
        // on the server 
        else {
            super.runBare();
        }
        
    }
    
    /**
     * This method is called by <code>runBare</code> when it
     * determines that the test is running locally (on the client).
     * If the cactus is "on", this method simply calls "super".  
     * If the cactus is "off", it runs the code copied from
     * JUnit, therefore disabling the override in Cactus.
     *  
     * @throws Throwable in case of any problems in the test
     */
    protected void runBareLocally() throws Throwable {

        if ( isCactusMode() ) {
            super.runBare();
        }
        else {
            setUp();
            try {
                runTestLocally();
            }
            finally {
                tearDown();
            }
        }
    }
    
    
    
    
    /**
     * Invokes the test method using reflection. 
     * Standard JUnit logic is duplicated here.
     * Using this method instead of <code>runTest()</code> enforces
     * that the test always runs locally (on the client). Cactus overrides <code>runTest()</code>
     * to run the test remotely, so using a different name ensures that it does not run.
     *
     * @see junit.framework.TestCase#runTest()
     *    
     * @exception Throwable if any exception is thrown
     */
    protected void runTestLocally() throws Throwable {
        assertNotNull( getName() );
        Method runMethod= null;
        try {
            // use getMethod to get all public inherited
            // methods. getDeclaredMethods returns all
            // methods of this class but excludes the
            // inherited ones.
            runMethod= getClass().getMethod( getName(), null);
        } catch (NoSuchMethodException e) {
            fail("Method \""+getName()+"\" not found");
        }
        if (!Modifier.isPublic(runMethod.getModifiers())) {
            fail("Method \""+getName()+"\" should be public");
        }

        try {
            runMethod.invoke(this, new Class[0]);
        }
        catch (InvocationTargetException e) {
            e.fillInStackTrace();
            throw e.getTargetException();
        }
        catch (IllegalAccessException e) {
            e.fillInStackTrace();
            throw e;
        }
    }
    

}