/*
 * 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.util.Random;

import junit.framework.Assert;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;
import org.exolab.castor.jdo.LockNotGrantedException;
import org.exolab.castor.jdo.PersistenceException;
import org.exolab.castor.jdo.TransactionAbortedException;
import org.exolab.castor.jdo.TransactionNotInProgressException;
import org.junit.Ignore;

/**
 * This is write thread that continuously create, load and modify, 
 * load and test if the modification is succeed, load and remove 
 * data objects and create again. This threads is the only thread 
 * that modifies data objects. If any inconsistency detected, for
 * examples, modification is not persisted, deleted object reappears,
 * created object disappeared, we can confirm there is problem in
 * the concurrent engine of Castor JDO.
 */
@Ignore
public class CreateDeleteThread extends Thread {
    /**
     * 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(CreateDeleteThread.class);


    private Database            _db;
    private int                 _cachetype;
    private int                 _trials;
    private boolean             _isDone;
    private Random              _random;
    
    /** Number of retrials attempt to load per each trial */
    public static final int NUM_OF_RETRIAL = 20;

    /** The base time in milliseconds between each attempts */
    public static final int SLEEP_BASE_TIME = 100;
    
    CreateDeleteThread(final TestCacheLeakage parent,
                               final JDOManager jdo,
                               final int cachetype, final int trials) throws Exception {
        _db = jdo.getDatabase();
        _cachetype = cachetype;
        _trials = trials;
        _random = new Random();
    }
    
    public void run() {
        try {
            LOG.info("start testing");
            Race tr;
            Race testrace;
            boolean succeed;
            int trials;

            out:
            for (int i = 0; i < _trials; i++) {
                // create, modified, delete object, depending on the
                // current cache type
                try {
                    switch (_cachetype) {
                    case 0:
                        testrace = new RaceCount();
                        testrace.setId(5);
                        break;
                    case 1:
                        testrace = new RaceTime();
                        testrace.setId(5);
                        break;
                    case 2:
                        testrace = new RaceNone();
                        testrace.setId(5);
                        break;
                    case 3:
                        testrace = new RaceUnlimited();
                        testrace.setId(5);
                        break;
                    default:
                        testrace = null;
                    }
 
                    _db.begin();
                    _db.create(testrace);
                    // may throw DuplicateIdentityException
                    _db.commit();

                    // load it and modify it
                    succeed = false;
                    trials = 0;
                    while (!succeed && trials < NUM_OF_RETRIAL) {
                        trials++;
                        try {
                            _db.begin();
                            tr = (Race) _db.load(TestCacheLeakage.getClassType(),
                                                 Race.DEFAULT_ID);
                            // may throw ObjectNotFoundException
                            // or LockNotGrantedException
                            tr.incValue1();
                            _db.commit();
                            succeed = true;
                        } catch (LockNotGrantedException ex) {
                            succeed = false;
                            rollbackAndSleep(trials);
                        } catch (TransactionAbortedException ex) {
                            succeed = false;
                            rollbackAndSleep(trials);
                        } 
                    }
                    
                    if (_db.isActive()) { _db.rollback(); }

                    // load it and release it
                    succeed = false;
                    trials = 0;
                    while (!succeed && trials < NUM_OF_RETRIAL) {
                        trials++;
                        try {
                            _db.begin();
                            tr = (Race) _db.load(TestCacheLeakage.getClassType(),
                                                 Race.DEFAULT_ID);
                            // may throw ObjectNotFoundException
                            // or LockNotGrantedException
                            _db.commit();
                            succeed = true;
                        } catch (LockNotGrantedException ex) {
                            succeed = false;
                            rollbackAndSleep(trials);
                        } 
                    }
                    if (_db.isActive()) { _db.rollback(); }

                    // load it and delete it
                    succeed = false;
                    trials = 0;
                    while (!succeed && trials < NUM_OF_RETRIAL) {
                        trials++;    
                        try {
                            _db.begin();
                            tr = (Race) _db.load(TestCacheLeakage.getClassType(),
                                                 Race.DEFAULT_ID);
                            // may throw ObjectNotFoundException
                            // or LockNotGrantedException
                            _db.remove(tr);
                            _db.commit();
                            succeed = true;
                        } catch (LockNotGrantedException ex) {
                            succeed = false;
                            rollbackAndSleep(trials);
                        } catch (TransactionAbortedException ex) {
                            succeed = false;
                            rollbackAndSleep(trials);
                        }
                    }
                    
                    if (_db.isActive()) { _db.rollback(); }
                    
                    if (!succeed) {
                        throw new Exception(
                                "Transaction can't lock the object within "
                                + trials + " trials");
                    }
                } catch (TransactionNotInProgressException ex) {
                    LOG.error("Thread <CreateDelete> will be killed. "
                            + "Unexcepted exception: ", ex);
                    rollbackWhenActive();
                    TestCacheLeakage.setErrLeak(true);
                    break out;
                } catch (PersistenceException ex) {
                    LOG.error("Thread <CreateDelete> will be killed. "
                            + "Unexcepted exception: ", ex);
                    rollbackWhenActive();
                    TestCacheLeakage.setErrLeak(true);
                    break out;
                } catch (Exception ex) {
                    LOG.error("Thread <CreateDelete> will be killed. "
                            + "Element not found (other): ", ex);
                    rollbackWhenActive();
                    TestCacheLeakage.setErrLeak(true);
                    break out;
                }
            }
        } finally {
            _isDone = true;
            try {
                _db.close();
            } catch (PersistenceException ex) {
                Assert.fail(ex.toString());
            }
        }
    }
    
    private void rollbackAndSleep(final int trials)
    throws TransactionNotInProgressException, InterruptedException {
        _db.rollback();
        // ethernet way of retry
        Thread.sleep((long) ((SLEEP_BASE_TIME ^ trials)
                             * _random.nextDouble()));
    }
    
    private void rollbackWhenActive() {
        if (_db.isActive()) {
            try { _db.rollback(); } catch (Exception ee) { }
        }
    }
    
    public boolean isDone() {
        return _isDone;
    }
}

