/*


    ========== licence begin GPL
    Copyright (C) 2002-2003 SAP AG

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end


*/

package com.sap.dbtech.jdbcext;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;
import java.util.Vector;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.XAConnection;
import javax.sql.XADataSource;
import javax.transaction.xa.Xid;

/**
 * XA Data Source implementation of SAP DB.
 */
public class XADataSourceSapDB
    extends DataSourceSapDBBase
    implements XADataSource, Referenceable
{
    private XATrace   xatrace;
    private boolean   initialized;
    private Hashtable transactions;
    private GarbageCollector garbagecollector;

    public XADataSourceSapDB()
    {
        initialized=false;
        transactions=new Hashtable();
        garbagecollector=new GarbageCollector(1000);
        garbagecollector.start();
    }

    public XAConnection getXAConnection()
    {
        Properties p=(Properties) connectProperties.clone();
        if(!initialized) {
            createXATrace();
        }
        return new XAConnectionSapDB(p, this, xatrace);
    }

    public XAConnection getXAConnection(String user, String password)
    {
        Properties p=(Properties) connectProperties.clone();
        p.setProperty("user", user);
        p.setProperty("password", password);
        if(!initialized) {
            createXATrace();
        }
        return new XAConnectionSapDB(p, this, xatrace);
    }
    
    private synchronized void createXATrace()
    {
        try {
            String s=getXATrace();
            if(s==null) {
                return;
                // s="c:\\xatrace.prt";
            }
            
            PrintWriter pw=getLogWriter();
            if(pw!=null && s.equalsIgnoreCase("true")) {
                this.xatrace = new XATrace(pw);
            }
            if(pw==null) {
                try {
                    this.xatrace = new XATrace(s);
                } catch(IOException ioex) {
                    return;
                }
            }
        } finally {
            this.initialized=true;
        }
    }

    XATransaction getXATransaction(Xid xid)
    {
        return (XATransaction) transactions.get(xid);
    }

    void addXATransaction(XATransaction xatrans)
    {
        transactions.put(xatrans.xid, xatrans);
    }

    void removeXATransaction(Xid xid) 
    {
        transactions.remove(xid);
    }
    
    Xid[] getAllPrepared()
    {
        Vector result=new Vector();
        Enumeration e=transactions.elements();
        while(e.hasMoreElements()) {
            XATransaction tx=(XATransaction)e.nextElement();
            if(tx.getStatus()==XATransaction.STATUS_PREPARED) {
                result.add(tx.xid);
            }
        }
        return (Xid[]) result.toArray(new Xid[result.size()]);
    }

    
    private Object outer()
    {
        return this;
    }
    
    class GarbageCollector
        extends Thread
    {
        ArrayList toBeRolledBack;
        ArrayList toBeRemoved;
        int       delay;
        
        GarbageCollector(int delay)
        {
            super("SAPDB XA Timeout Manager " + outer().hashCode());
            toBeRolledBack=new ArrayList();
            toBeRemoved=new ArrayList();
            this.delay=delay;
        }
        
        public void run()
        {
            while(true) {

                try { Thread.sleep(delay); } catch(InterruptedException ignore) {}
                // System.err.println("XA Garbage collection start.");
                toBeRemoved.clear();
                toBeRolledBack.clear();
                synchronized(outer()) {
                    Enumeration e=transactions.elements();
                    long t=System.currentTimeMillis();
                    while(e.hasMoreElements()) {
                        XATransaction tx=(XATransaction)e.nextElement();
                        if(tx.getStatus()==XATransaction.STATUS_ROLLED_BACK) {
                            toBeRemoved.add(tx.xid);
                        } else {
                            if(tx.timeOutTime < t) {
                                toBeRolledBack.add(tx.xid);
                            }
                        }
                    }
                }
                
                // at first the rollbacks ...
                Iterator i=toBeRolledBack.iterator();
                while(i.hasNext()) {
                    Xid xid=(Xid) i.next();
                    if(xatrace!=null) xatrace.trace(xid, "rollback (timeout)");

                    synchronized(outer()) {
                        XATransaction tx=getXATransaction(xid);
                        if(tx!=null) {
                            try {
                                tx.rollbackRelease();
                            } catch(SQLException sqlEx) {
                                try { tx.physicalConnection.close(); } catch(SQLException ignore) {}
                                tx.setStatus(XATransaction.STATUS_ROLLED_BACK);
                            }
                        }
                    }
                }
                
                Iterator j=toBeRemoved.iterator();
                while(j.hasNext()) {
                    Xid xid=(Xid) j.next();
                    if(xatrace!=null) xatrace.trace(xid, "forget (timeout)");
                    synchronized(outer()) {
                        removeXATransaction(xid);
                    }
                }
                // System.err.println("XA Garbage collection completed.");
            }
        }
            
    }

    public Reference getReference() throws NamingException
    {
        return createReference(getClass().getName());
    }

}
