/*
 * Copyright 2004-2013 the Seasar Foundation and the Others.
 *
 * 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.seasar.extension.jta;

import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.Transaction;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import junit.framework.TestCase;

import org.seasar.extension.jta.xa.DefaultXAResource;
import org.seasar.framework.util.TransactionUtil;

/**
 * @author higa
 * 
 */
public class TransactionImplTest extends TestCase {

    private TransactionImpl tx_;

    protected void setUp() throws Exception {
        tx_ = new TransactionImpl();
    }

    /**
     * @throws Exception
     */
    public void testEquals() throws Exception {
        assertEquals("1", tx_, tx_);
        assertTrue("2", !tx_.equals(null));
        assertTrue("3", !tx_.equals(new TransactionImpl()));
        assertTrue("4", !tx_.equals("test"));
    }

    /**
     * @throws Exception
     */
    public void testBegin() throws Exception {
        tx_.begin();
        assertEquals("2", Status.STATUS_ACTIVE, tx_.getStatus());
    }

    /**
     * @throws Exception
     */
    public void testSuspend() throws Exception {
        DefaultXAResource xaRes = new DefaultXAResource();
        tx_.begin();
        tx_.enlistResource(xaRes);
        tx_.suspend();
        assertEquals("1", true, tx_.isSuspended());
        assertEquals("2", DefaultXAResource.RS_SUSPENDED, xaRes.getStatus());
        try {
            tx_.suspend();
            fail("3");
        } catch (IllegalStateException ex) {
            System.out.println(ex);
        }
        tx_.resume();
        tx_.commit();
        try {
            tx_.suspend();
            fail("4");
        } catch (IllegalStateException ex) {
            System.out.println(ex);
        }
    }

    /**
     * @throws Exception
     */
    public void testSuspend_MarkedRollback() throws Exception {
        DefaultXAResource xaRes = new DefaultXAResource();
        tx_.begin();
        tx_.enlistResource(xaRes);
        tx_.setRollbackOnly();
        tx_.suspend();
        assertEquals("1", true, tx_.isSuspended());
        assertEquals("2", DefaultXAResource.RS_SUSPENDED, xaRes.getStatus());
        try {
            tx_.suspend();
            fail("3");
        } catch (IllegalStateException ex) {
            System.out.println(ex);
        }
        tx_.resume();
        tx_.rollback();
        try {
            tx_.suspend();
            fail("4");
        } catch (IllegalStateException ex) {
            System.out.println(ex);
        }
    }

    /**
     * @throws Exception
     */
    public void testResume() throws Exception {
        DefaultXAResource xaRes = new DefaultXAResource();
        tx_.begin();
        tx_.enlistResource(xaRes);
        tx_.suspend();
        tx_.resume();
        assertEquals("1", false, tx_.isSuspended());
        assertEquals("2", DefaultXAResource.RS_ACTIVE, xaRes.getStatus());
        try {
            tx_.resume();
            fail("3");
        } catch (IllegalStateException ex) {
            System.out.println(ex);
        }
    }

    /**
     * @throws Exception
     */
    public void testOnePhaseCommit() throws Exception {
        DefaultXAResource xaRes = new DefaultXAResource();
        Sync sync = new Sync(tx_);
        tx_.begin();
        tx_.enlistResource(xaRes);
        tx_.registerSynchronization(sync);
        tx_.commit();
        assertTrue("1", sync.beforeCompleted_);
        assertTrue("2", sync.afterCompleted_);
        assertEquals("3", Status.STATUS_COMMITTED, sync.completedStatus_);
        assertEquals("4", DefaultXAResource.RS_NONE, xaRes.getStatus());
    }

    /**
     * @throws Exception
     */
    public void testTwoPhaseCommit() throws Exception {
        DefaultXAResource xaRes = new DefaultXAResource();
        DefaultXAResource xaRes2 = new DefaultXAResource();
        tx_.begin();
        tx_.enlistResource(xaRes);
        tx_.enlistResource(xaRes2);
        tx_.commit();
        assertEquals("1", Status.STATUS_NO_TRANSACTION, tx_.getStatus());
        assertEquals("2", DefaultXAResource.RS_NONE, xaRes.getStatus());
        assertEquals("3", DefaultXAResource.RS_NONE, xaRes2.getStatus());
    }

    /**
     * @throws Exception
     */
    public void testLastResourceCommitOptimization() throws Exception {
        final boolean[] onePhaseFlags = new boolean[3];
        DefaultXAResource[] xaRes = new DefaultXAResource[3];
        for (int i = 0; i < 3; ++i) {
            final int index = i;
            xaRes[i] = new DefaultXAResource() {
                protected void doCommit(Xid xid, boolean onePhase)
                        throws XAException {
                    onePhaseFlags[index] = onePhase;
                }
            };
        }
        tx_.begin();
        for (int i = 0; i < 3; ++i) {
            tx_.enlistResource(xaRes[i]);
        }
        tx_.commit();
        assertEquals("1", Status.STATUS_NO_TRANSACTION, tx_.getStatus());
        assertTrue("2", onePhaseFlags[0]);
        assertFalse("3", onePhaseFlags[1]);
        assertFalse("4", onePhaseFlags[2]);
    }

    /**
     * @throws Exception
     */
    public void testCommitForException() throws Exception {
        DefaultXAResource xaRes = new DefaultXAResource();
        tx_.begin();
        tx_.enlistResource(xaRes);
        tx_.suspend();
        try {
            tx_.commit();
            fail("1");
        } catch (IllegalStateException ex) {
            System.out.println(ex);
        }
        tx_.begin();
        tx_.commit();
        try {
            tx_.commit();
            fail("2");
        } catch (IllegalStateException ex) {
            System.out.println(ex);
        }
    }

    /**
     * @throws Exception
     */
    public void testOnePhaseCommitFail() throws Exception {
        DefaultXAResource xaRes = new DefaultXAResource() {
            protected int doPrepare(Xid xid) throws XAException {
                TransactionImplTest.fail("0");
                return XAResource.XA_OK;
            }

            protected void doCommit(Xid xid, boolean onePhase)
                    throws XAException {
                throw new XAException();
            }
        };
        tx_.begin();
        tx_.enlistResource(xaRes);
        try {
            tx_.commit();
            fail("1");
        } catch (RollbackException expected) {
            System.out.println(expected);
        }
    }

    /**
     * @throws Exception
     */
    public void testTwoPhaseCommitFail() throws Exception {
        final boolean[] result = new boolean[4];
        DefaultXAResource xaRes0 = new DefaultXAResource() {
            protected int doPrepare(Xid xid) throws XAException {
                return XAResource.XA_RDONLY;
            }

            protected void doCommit(Xid xid, boolean onePhase)
                    throws XAException {
                TransactionImplTest.fail("0");
            }

            protected void doRollback(Xid xid) throws XAException {
                result[0] = true;
            }
        };
        DefaultXAResource xaRes1 = new DefaultXAResource() {
            protected int doPrepare(Xid xid) throws XAException {
                return XAResource.XA_OK;
            }

            protected void doCommit(Xid xid, boolean onePhase)
                    throws XAException {
                TransactionImplTest.fail("1");
            }

            protected void doRollback(Xid xid) throws XAException {
                result[1] = true;
            }
        };
        DefaultXAResource xaRes2 = new DefaultXAResource() {
            protected int doPrepare(Xid xid) throws XAException {
                throw new XAException();
            }

            protected void doCommit(Xid xid, boolean onePhase)
                    throws XAException {
                TransactionImplTest.fail("2");
            }

            protected void doRollback(Xid xid) throws XAException {
                result[2] = true;
            }
        };
        DefaultXAResource xaRes3 = new DefaultXAResource() {
            protected int doPrepare(Xid xid) throws XAException {
                TransactionImplTest.fail("3");
                return XAResource.XA_OK;
            }

            protected void doCommit(Xid xid, boolean onePhase)
                    throws XAException {
                TransactionImplTest.fail("4");
            }

            protected void doRollback(Xid xid) throws XAException {
                result[3] = true;
            }
        };

        tx_.begin();
        tx_.enlistResource(xaRes3);
        tx_.enlistResource(xaRes2);
        tx_.enlistResource(xaRes1);
        tx_.enlistResource(xaRes0);
        try {
            tx_.commit();
            fail("5");
        } catch (RollbackException ex) {
            System.out.println(ex);
        }
        assertFalse("6", result[0]);
        assertTrue("7", result[1]);
        assertFalse("8", result[2]);
        assertTrue("9", result[3]);
    }

    /**
     * @throws Exception
     */
    public void testRollback() throws Exception {
        DefaultXAResource xaRes = new DefaultXAResource();
        Sync sync = new Sync(tx_);
        tx_.begin();
        tx_.enlistResource(xaRes);
        tx_.registerSynchronization(sync);
        tx_.rollback();
        assertFalse("1", sync.beforeCompleted_);
        assertTrue("2", sync.afterCompleted_);
        assertEquals("3", Status.STATUS_ROLLEDBACK, sync.completedStatus_);
        assertEquals("4", DefaultXAResource.RS_NONE, xaRes.getStatus());
    }

    /**
     * @throws Exception
     */
    public void testRollbackForException() throws Exception {
        DefaultXAResource xaRes = new DefaultXAResource();
        tx_.begin();
        tx_.enlistResource(xaRes);
        tx_.suspend();
        try {
            tx_.rollback();
            fail("1");
        } catch (IllegalStateException ex) {
            System.out.println(ex);
        }
        tx_.begin();
        tx_.rollback();
        try {
            tx_.rollback();
            fail("2");
        } catch (IllegalStateException ex) {
            System.out.println(ex);
        }
    }

    /**
     * @throws Exception
     */
    public void testSetRollbackOnly() throws Exception {
        tx_.begin();
        tx_.setRollbackOnly();
        assertEquals("1", Status.STATUS_MARKED_ROLLBACK, tx_.getStatus());
    }

    /**
     * @throws Exception
     */
    public void testSetRollbackOnlyForException() throws Exception {
        tx_.begin();
        tx_.commit();
        try {
            tx_.setRollbackOnly();
            fail("1");
        } catch (IllegalStateException ex) {
            System.out.println(ex);
        }
    }

    /**
     * @throws Exception
     */
    public void testDelistResource() throws Exception {
        DefaultXAResource xaRes = new DefaultXAResource();
        tx_.begin();
        tx_.enlistResource(xaRes);
        tx_.delistResource(xaRes, XAResource.TMSUCCESS);
        assertEquals("1", DefaultXAResource.RS_SUCCESS, xaRes.getStatus());
    }

    /**
     * @throws Exception
     */
    public void testDelistResourceForException() throws Exception {
        tx_.begin();
        DefaultXAResource xaRes = new DefaultXAResource();
        try {
            tx_.delistResource(xaRes, XAResource.TMSUCCESS);
        } catch (IllegalStateException ex) {
            System.out.println(ex);
        }
        tx_.suspend();
        tx_.resume();
        tx_.commit();
        try {
            tx_.delistResource(xaRes, XAResource.TMSUCCESS);
        } catch (IllegalStateException ex) {
            System.out.println(ex);
        }
    }

    /**
     * @throws Exception
     */
    public void testBeforeCompletion() throws Exception {
        tx_.begin();
        Sync sync = new Sync(tx_);
        tx_.registerSynchronization(sync);
        tx_.registerSynchronization(new ExceptionSync());
        try {
            tx_.commit();
            fail("1");
        } catch (RollbackException ex) {
            System.out.println(ex);
        }
        assertTrue(sync.beforeCompleted_);
        assertTrue(sync.afterCompleted_);
    }

    private class Sync implements Synchronization {

        private boolean beforeCompleted_ = false;

        private boolean afterCompleted_ = false;

        private int completedStatus_ = Status.STATUS_UNKNOWN;

        private Transaction tx_;

        /**
         * @param tx
         */
        public Sync(Transaction tx) {
            tx_ = tx;
        }

        public void beforeCompletion() {
            beforeCompleted_ = true;
        }

        public void afterCompletion(int status) {
            assertEquals(Status.STATUS_NO_TRANSACTION, TransactionUtil
                    .getStatus(tx_));
            afterCompleted_ = true;
            completedStatus_ = status;
        }
    }

    private class ExceptionSync implements Synchronization {

        public void beforeCompletion() {
            throw new RuntimeException("hoge");
        }

        public void afterCompletion(int status) {
        }
    }

}
