/*
 * Copyright 2009 Udai Gupta, Ralf Joachim
 *
 * Licensed 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.castor.cpa.test.test07;

import java.sql.SQLException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.cpa.test.framework.CPATestCase;
import org.castor.cpa.test.framework.xml.types.DatabaseEngineType;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;
import org.exolab.castor.jdo.PersistenceException;

/**
 * This is a concurrent stress test. This test creates one writing thread
 * that continuously creates, loads and modifies data objects. It loads 
 * and test if the modifications has succeed, loads again and removes 
 * data objects and creates a new object with the same identity again....; 
 * multiple read threads are created and continuously read the data objects 
 * from transactions and commit the transactions without modifying any
 * data object. The 'read and commit' actions essentially lock and unlock 
 * the data object. These tests pass if all modifications to data objects 
 * via the write thread is properly persisted and there is no deadlock 
 * occurring. Passing the tests confirm Castor JDO properly lock and release 
 * objects. 
 * <p>
 * Tests are performed on all four different cache types. (note, these tests 
 * may failed if the number of JDBC connections available to Castor JDO is 
 * too small. To resolve the problem, reduce the number of read threads or 
 * increase the available connections.)
 */
public final class TestCacheLeakage extends CPATestCase {
    /**
     * The <a href="http://jakarta.apache.org/commons/logging/">Jakarta
     * Commons Logging</a> instance used for all logging.
     */
    private static final Log LOG = LogFactory.getLog(TestCacheLeakage.class);
    
    private static final int COUNT_LIMITED = 0;

    private static final int TIME_LIMITED  = 1;

    private static final int NO_CACHE      = 2;

    private static final int UNLIMITED     = 3;

    /** Number of target data objects to be created and deleted */
    private static final int NUM_OF_CREATE_DELETE = 10;

    /** Number of trial of loads and releases on each object */
    private static final int NUM_OF_READ = 50;

    /** Number of load and releases of read threads to race on target */
    private static final int NUM_OF_READ_THREADS = 5;

    private static final String DBNAME = "test07";
    private static final String MAPPING = "/org/castor/cpa/test/test07/mapping.xml";
    private Database _db;
    
    private JDOManager _jdo;

    /** The java class of the target data objects */
    private static Class<?> _classType;

    /** The cache type used in the current test */
    private static int _cacheType;

    /** Indicates leakage detected */
    private static boolean _errLeak;

    /** Indicates error detected */
    private static boolean _errCount;

    /**
     * Constructor
     *
     * @param category the test suite of these test cases
     */
    public TestCacheLeakage(final String name) {
        super(name);
        _errLeak = false;
        _errCount = false;
    }

    // Test are only included/excluded for engines that have been tested with this test suite.

    public boolean include(final DatabaseEngineType engine) {
        return (engine == DatabaseEngineType.DERBY)
            || (engine == DatabaseEngineType.HSQL)
            || (engine == DatabaseEngineType.MYSQL)
            || (engine == DatabaseEngineType.ORACLE)
            || (engine == DatabaseEngineType.POSTGRESQL);
    }

    /**
     * Get a JDO Database and get a JDBC connection
     */
    public void setUp() throws Exception {
        _jdo = getJDOManager(DBNAME, MAPPING);
        _db = _jdo.getDatabase();
    }

    /**
     * Run the stress test for four different cache setting
     */
    public void testCacheLeakage() throws Exception {
        _cacheType = TIME_LIMITED;
        runOnce();

        _cacheType = NO_CACHE;
        runOnce();

        _cacheType = UNLIMITED;
        runOnce();

        _cacheType = COUNT_LIMITED; 
        runOnce();

        assertTrue("Element leak not detected!", !_errLeak);
        assertTrue("Race condition not happened!", !_errCount);
    }

    public void tearDown() throws PersistenceException, SQLException {
        if (_db.isActive()) { _db.rollback(); }
        _db.close();
    }

    /**
     * Helper class to run the stress tests once for a cache type.
     */
    public void runOnce() throws Exception {
        // clear the table
        _db.begin();
        int del = _db.getJdbcConnection().createStatement().executeUpdate(
                "DELETE FROM test07_race");
        _db.commit();
        LOG.debug("row(s) deleted in table core_race: " + del);


        // set the className and classType to be used
        switch (_cacheType) {
        case COUNT_LIMITED:
            _classType = RaceCount.class;
            break;
        case TIME_LIMITED:
            _classType = RaceTime.class;
            break;
        case NO_CACHE:
            _classType = RaceNone.class;
            break;
        case UNLIMITED:
            _classType = RaceUnlimited.class;
            break;
        default:
            LOG.error("Unknown cache type");
        }

        CreateDeleteThread cdThread = new CreateDeleteThread(
                this, _jdo, _cacheType, NUM_OF_CREATE_DELETE);

        ReadThread[] rThread =  new ReadThread[NUM_OF_READ_THREADS];
        for (int i = 0; i < NUM_OF_READ_THREADS; i++) {
            rThread[i] = new ReadThread(this, cdThread, _jdo, NUM_OF_READ);
            rThread[i].start();
        }

        cdThread.start();
        
        // Polling the test case to see if it is finished
        try {
            while (!cdThread.isDone()) {
                Thread.sleep(500);
            }

            // Joins all the finished threads
            cdThread.join();
            for (int i = 0; i < NUM_OF_READ_THREADS; i++) {
                rThread[i].join();
            }
        } catch (InterruptedException ex) {
            fail(ex.toString());
        }
    }

    public static Class<?> getClassType() {
        return _classType;
    }

    public static void setClassType(final Class<?> classType) {
        _classType = classType;
    }

    public static int getCacheType() {
        return _cacheType;
    }

    public static void setCacheType(final int cacheType) {
        _cacheType = cacheType;
    }

    public static boolean isErrLeak() {
        return _errLeak;
    }

    public static void setErrLeak(final boolean errLeak) {
        _errLeak = errLeak;
    }

    public static boolean isErrCount() {
        return _errCount;
    }

    public static void setErrCount(final boolean errCount) {
        _errCount = errCount;
    }
}