/*
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@objectweb.org
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas.stests.manac;

import java.rmi.RemoteException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Enumeration;
import javax.ejb.CreateException;
import javax.ejb.EJBException;
import javax.ejb.FinderException;
import javax.ejb.RemoveException;
import javax.ejb.EJBObject;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.ejb.SessionSynchronization;
import javax.ejb.TransactionRolledbackLocalException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import javax.transaction.NotSupportedException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;

import org.objectweb.jonas.common.Log;
import org.objectweb.util.monolog.api.Logger;
import org.objectweb.util.monolog.api.BasicLevel;

/**
 * Manager Implementation
 * @author Philippe Durieux
 */
public class ManagerSF implements SessionBean {

    static protected Logger logger = null;
    SessionContext ejbContext;
    ManacLocalHome manacLocalHome = null;
    int delay = 0;
    int c1 = 0;
    int c2 = 0;
    int d1 = 0;
    int d2 = 0;
    ManacLocal cred1, cred2, deb1, deb2;
    int initialValue = 1000;
    int value = 10;
    int accnb;

    // ------------------------------------------------------------------
    // init DataBase for Manac beans
    // ------------------------------------------------------------------
    private void initDB() {
	logger.log(BasicLevel.DEBUG, "");

	// Get my DataSource from JNDI
	DataSource ds = null;
	InitialContext ictx = null;
	try {
	    ictx = new InitialContext(); 
	} catch (Exception e) {
	    logger.log(BasicLevel.ERROR, "new InitialContext() : " + e);
	    throw new EJBException("Cannot get JNDI InitialContext");
	}
	try {
	    ds = (DataSource) ictx.lookup("java:comp/env/jdbc/mydb");
	} catch (Exception e) {
	    logger.log(BasicLevel.ERROR, "cannot lookup datasource " + e);
	    throw new EJBException("cannot lookup datasource");
	}

	// Drop table
	Connection conn = null;
	Statement stmt = null;
	// myTable must be <jdbc-table-name> from jonas-xml file (Manac bean)
	String myTable = "stestsmanac"; 
	try {
	    conn = ds.getConnection();
	    stmt = conn.createStatement();
	    stmt.execute("drop table " + myTable);
	    stmt.close();
	} catch(SQLException e) {
	    // The first time, table will not exist.
	    logger.log(BasicLevel.INFO, "Exception in dropTable : "+e);
	}

	// Create table.
	try {
	    stmt = conn.createStatement();
	    stmt.execute("create table " + myTable + "(c_name varchar(30) not null primary key, c_num integer, c_balance integer)");
	    stmt.close();
	    conn.close();
	} catch(SQLException e) {
	    logger.log(BasicLevel.ERROR, "Exception in createTable : "+e);
	    throw new EJBException("Exception in createTable");
	}
    }

    // ------------------------------------------------------------------
    // SessionBean implementation
    // ------------------------------------------------------------------

    /** 
     * Set the associated session context. The container calls this method
     * after the instance creation. 
     * The enterprise Bean instance should store the reference to the context
     * object in an instance variable. 
     * This method is called with no transaction context.
     *
     * @param sessionContext A SessionContext interface for the instance.
     * @throws EJBException Thrown by the method to indicate a failure caused by
     * a system-level error.
     */
    public void setSessionContext(SessionContext ctx) {
	if (logger == null)
	    logger = Log.getLogger(Log.JONAS_TESTS_PREFIX);
	logger.log(BasicLevel.DEBUG, "");
	ejbContext = ctx;
    }
	
    /**
     * A container invokes this method before it ends the life of the session object. 
     * This happens as a result of a client's invoking a remove operation, or when a 
     *  container decides to terminate the session object after a timeout. 
     * This method is called with no transaction context. 
     *
     * @throws EJBException Thrown by the method to indicate a failure caused by
     * a system-level error.
     */
    public void ejbRemove() {
	logger.log(BasicLevel.DEBUG, "");
    }
	
    /**
     * The Session bean must define 1 or more ejbCreate methods.
     *
     * @throws CreateException Failure to create a session EJB object.
     */
    public void ejbCreate(int ival) throws CreateException {
	logger.log(BasicLevel.DEBUG, "");

	// lookup ManacLocalHome
	try {
	    Context ictx = new InitialContext();
	    manacLocalHome = (ManacLocalHome) ictx.lookup("java:comp/env/ejb/manac");
	} catch (NamingException e) {
	    logger.log(BasicLevel.ERROR, "Cannot get ManacLocalHome:"+e);
	    throw new CreateException("Cannot get ManacLocalHome");
	}

	initialValue = ival;
    }

    /**
     * A container invokes this method on an instance before the instance
     * becomes disassociated with a specific EJB object.
     */
    public void ejbPassivate() {
	logger.log(BasicLevel.DEBUG, "");
    }

    /**
     * A container invokes this method when the instance is taken out of 
     * the pool of available instances to become associated with a specific 
     * EJB object.
     */
    public void ejbActivate() {
	logger.log(BasicLevel.DEBUG, "");
    }
    
    // ------------------------------------------------------------------
    // Manager implementation
    // ------------------------------------------------------------------

    public void createAll(int nb) throws RemoteException {
	logger.log(BasicLevel.DEBUG, "");

	// init database for Manac bean
	initDB();

	// create accounts
	accnb = 0;
	for (int i = 0; i < nb; i++) {
	    try {
		manacLocalHome.create(i, initialValue);
		accnb++;
	    } catch (CreateException e) {
		logger.log(BasicLevel.ERROR, "createAll:\n"+e);
		throw new RemoteException("Cannot create Manac");
	    }
	}
    }

    public void reinitAll() throws RemoteException {
	logger.log(BasicLevel.DEBUG, "");
	try {
	    Enumeration enum = manacLocalHome.findAll();
	    while (enum.hasMoreElements()) {
		ManacLocal a = (ManacLocal) enum.nextElement();
		a.setBalance(initialValue);
	    }
	} catch (Exception e) {
	    logger.log(BasicLevel.ERROR, "reinitAll:"+e);
	}
    }

    public void setDelay(int d) throws RemoteException {
	logger.log(BasicLevel.DEBUG, "");
	delay = d;
    }

    public void setAccounts(int d1, int d2, int c1, int c2) throws RemoteException {
	logger.log(BasicLevel.DEBUG, "d1="+d1+" d2="+d2+" c1="+c1+" c2="+c2);
	this.d1 = d1;
	this.d2 = d2;
	this.c1 = c1;
	this.c2 = c2;
	try {
	    deb1 = manacLocalHome.findByNum(d1);
	    deb2 = manacLocalHome.findByNum(d2);
	    cred1 = manacLocalHome.findByNum(c1);
	    cred2 = manacLocalHome.findByNum(c2);
	} catch (FinderException e) {
	    logger.log(BasicLevel.ERROR, "Cannot find manac bean:"+e);
	    throw new RemoteException("Cannot find manac bean");
	}
    }

    public void setValue(int v) throws RemoteException {
	logger.log(BasicLevel.DEBUG, "");
	this.value = v;
    }

    public void movement() throws RemoteException {
	logger.log(BasicLevel.DEBUG, "move "+value+" from "+d1+","+d2+" to "+c1+","+c2);

	// credit accounts first because we don't want a rollback if
	// same account is debited and credited in the same operation.
	try {
	    cred1.credit(value);
	    cred2.credit(value);
	} catch (EJBException e) {
	    logger.log(BasicLevel.ERROR, "Cannot credit account:"+e);
	    throw new RemoteException("ManagerSF: Cannot credit account");
	}

	// wait a little if delay
	if (delay > 0) {
	    sleep(delay);
	}
	    
	// debit accounts
	try {
	    deb1.debit(value);
	    deb2.debit(value);
	} catch (TransactionRolledbackLocalException e) {
	    logger.log(BasicLevel.WARN, "Rollback transaction");
	} catch (EJBException e) {
	    logger.log(BasicLevel.WARN, "debit:"+e);
	}
    }

    public void sleep(int milliseconds) {
	try {
	    Thread.currentThread().sleep(milliseconds);
	} catch (InterruptedException e) {
	}
    }

    public boolean checkAccount(int a) throws RemoteException {
	logger.log(BasicLevel.DEBUG, "");

	boolean ret = false;
	ManacLocal m = null;
	
	// retry several times, because this operation may be rolledback
	// in case of deadlock.
	Exception exc = null;
	int retry;
	for (retry = 0; retry < 5; retry++) {
	    try {
		m = manacLocalHome.findByNum(a);
		int b = m.getBalance();
		if (b >= 0) {
		    ret = true;
		} else {
		    logger.log(BasicLevel.WARN, "bad balance="+b);
		}
		return ret;
	    } catch (Exception e) {
		exc = e;
		logger.log(BasicLevel.WARN, "retrying");
		sleep(500*(retry+1));
	    }
	}
	logger.log(BasicLevel.WARN, "cannot check account: "+exc);
	return ret;
    }

    public boolean checkAll() throws RemoteException {
	logger.log(BasicLevel.DEBUG, "");

	int count = 0;
	int total = 0;
	try {
	    Enumeration enum = manacLocalHome.findAll();
	    while (enum.hasMoreElements()) {
		count++;
		ManacLocal a = (ManacLocal) enum.nextElement();
		int balance = a.getBalance();
		if (balance < 0) {
		    logger.log(BasicLevel.ERROR, "checkAllAccounts: bad balance: "+balance);
		    return false;
		}
		String name = a.getName();
		logger.log(BasicLevel.INFO, name+" : FINAL BALANCE="+balance);
		total += balance;
	    }
	} catch (Exception e) {
	    logger.log(BasicLevel.ERROR, "checkAllAccounts:"+e);
	    return false;
	}
	int exp = initialValue * count;
	if (total != exp) {
	    logger.log(BasicLevel.ERROR, "checkAllAccounts: bad total: "+total+" (expected: "+exp+")");
	    return false;
	}
	logger.log(BasicLevel.DEBUG, "total="+total);
	return true;
    }

}
