import java.sql.Connection;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Random;

import javax.sql.XAConnection;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import org.firebirdsql.pool.FBConnectionPoolDataSource;

/**
 * Prior to running the test, adjust the hard-coded connection parameters, and create test table:
 * 
 * CREATE TABLE foo (bar INTEGER);
 * 
 * Expected output if it the driver could issue an out-of-band commit:
 * 
 * Initialized
 * Doing first update
 * Doing second update
 * Trying to commit
 * Commit finished
 * Second update committed
 * 
 * Instead, it will hang at "Trying to commit" line until DEADLOCK_TIMEOUT is reached (default
 * 10 seconds).
 */

public class XATest {
	public static void main(String[] args) throws Exception {
		Xid initxid = new CustomXid(1); 
		
		FBConnectionPoolDataSource ds = new FBConnectionPoolDataSource();
		ds.setDatabase("foo.fdb");
		ds.setUserName("SYSDBA");
		ds.setPassword("masterkey");

		XAConnection xaconn1 = ds.getXAConnection();
		XAResource xares1 = xaconn1.getXAResource();
		Connection conn1 = xaconn1.getConnection();

		XAConnection xaconn2 = ds.getXAConnection();
		XAResource xares2 = xaconn2.getXAResource();
		Connection conn2 = xaconn2.getConnection();
		
		// Initialize table
		xares1.start(initxid, XAResource.TMNOFLAGS);
		Statement stmt = conn1.createStatement();
		stmt.executeUpdate("DELETE FROM  foo");
		stmt.executeUpdate("INSERT INTO foo VALUES (1234)");
		xares1.end(initxid, XAResource.TMSUCCESS);
		xares1.commit(initxid, true);
		
		System.out.println("Initialized");
		
		// Start updating transaction that is ended but not committed
		Xid neverfinishxid = new CustomXid(2);
		xares1.start(neverfinishxid, XAResource.TMNOFLAGS);
		System.out.println("Doing first update");
		conn1.createStatement().executeUpdate("UPDATE foo SET bar = 5678");
		xares1.end(neverfinishxid, XAResource.TMSUCCESS);

		// Launch a committer thread that will try to commit neverfinishxid after a while
		Committer committer = new Committer();
		committer.xares = xares1;
		committer.commitThis = neverfinishxid;
		Thread t = new Thread(committer);
		t.start();

		// Start another update that blocks because it waits for neverfinishxid
		Xid blockxid = new CustomXid(3);
		xares2.start(blockxid, XAResource.TMNOFLAGS);
		System.out.println("Doing second update");
//		conn2.createStatement().executeUpdate("INSERT INTO foo VALUES(0000)");
		conn2.createStatement().executeUpdate("UPDATE foo SET bar = 0000");
		System.out.println("Second update committed");
		xares2.end(blockxid, XAResource.TMSUCCESS);
    	xares2.commit(blockxid, true);

		conn1.close();
		conn2.close();
		
		t.join();
		
		xaconn1.close();
		xaconn2.close();
	}

	public static class Committer implements Runnable
	{
		Xid commitThis;
		XAResource xares;
		
		public void run()
		{
			try {
				Thread.sleep(2000);
				System.out.println("Trying to commit");
				xares.commit(commitThis, true);
				System.out.println("Commit finished");
			} catch(Exception ex)
			{
				ex.printStackTrace();
			}
		}
	}

    static class CustomXid implements Xid {
        private static Random rand = new Random(System.currentTimeMillis());
        byte[] gtrid = new byte[Xid.MAXGTRIDSIZE];
        byte[] bqual = new byte[Xid.MAXBQUALSIZE];

        CustomXid(int i)
        {
            rand.nextBytes(gtrid);
            gtrid[0] = (byte)i;
            gtrid[1] = (byte)i;
            gtrid[2] = (byte)i;
            gtrid[3] = (byte)i;
            gtrid[4] = (byte)i;
            bqual[0] = 4;
            bqual[1] = 5;
            bqual[2] = 6;
        }

        public int getFormatId() {
            return 0;
        }


        public byte[] getGlobalTransactionId() {
            return gtrid;
        }

        public byte[] getBranchQualifier() {
            return bqual;
        }
        public boolean equals(Object o) {
            Xid other = (Xid)o;
            if (other.getFormatId() != this.getFormatId())
                return false;
            if (!Arrays.equals(other.getBranchQualifier(), this.getBranchQualifier()))
                return false;
            if (!Arrays.equals(other.getGlobalTransactionId(), this.getGlobalTransactionId()))
                return false;

            return true;
        }
    }
}
