/*
 * The contents of this file are subject to the JOnAS Public License Version 
 * 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License on the JOnAS web site.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 * See the License for the specific terms governing rights and limitations under 
 * the License.
 *
 * The Original Code is JOnAS application server code released July 1999.
 *
 * The Initial Developer of the Original Code is Bull S.A.
 * The Original Code and portions created by Bull S.A. are
 *    Copyright (C) 1999 Bull S.A. All Rights Reserved.
 *
 * Contributor(s):
 * Markus Karg: Novell port.
 *
 * --------------------------------------------------------------------------
 * $Id: JEntityHome.java,v 1.8 2001/04/27 14:11:02 jonas Exp $
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas_ejb.container;

import java.util.Enumeration;
import java.util.Hashtable;
import java.rmi.RemoteException;
import javax.ejb.EntityBean;
import javax.ejb.EJBContext;
import javax.naming.Context;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionRolledbackException;
import org.objectweb.jonas_ejb.deployment.api.EntityDesc;
import org.objectweb.jonas_ejb.deployment.api.EntityCmpDesc;


/**
 * Class inherited by all the Home object for entity beans
 */
abstract public class JEntityHome extends JBeanHome {
    
    /**
     * @serial
     */
    private Hashtable ejbobjects = new Hashtable();

    /**
     * @serial
     */
    private Object datasource = null;

    /**
     * @serial
     */
    private JContextEntity available = null;

    /**
     * @serial
     */
    private boolean isContainerManaged;

    /**
     * true if the bean is Reentrant: i.e. instances can be accessed concurrently
     * by different threads.
     * @serial
     */
    private boolean isReentrant;

    /**
     * @serial
     */
    protected String primaryKeyClassName = null;


    /**
     * constructor
     * The DataSourceImpl param value may be null
     */
    public JEntityHome(EntityDesc desc, String beanName, JContainerImpl cont, Object ds) 
	throws RemoteException {

	super(desc, beanName, cont);

	if (TraceEjb.isDebug) TraceEjb.debug("JEntityHome constructor for "+ beanName);

	this.isContainerManaged = (desc instanceof EntityCmpDesc);
	this.isReentrant = desc.isReentrant();
	this.datasource = ds;
	this.primaryKeyClassName = desc.getPrimaryKeyClass().getName();

	JBeanMetaData bbdm = new JBeanMetaData(this,
					       desc.getHomeClass().getName(),
					       desc.getRemoteClass().getName(),
					       primaryKeyClassName,
					       false,
					       false);
	this.setEJBMetaData(bbdm);
    }


    /**
     * Add the EJBObject in the list kept in the Home. The primary key
     * is used as a key in a hashtable to retrieve it later.
     * The callers must be synchronized
     *
     * @param ejbObject - the EJBObject
     */
    public void putEJBObject(JBeanEntity ejbObject) {

	TraceEjb.debug("JEntityHome.putEJBObject");
	
	// Get the primary key: should always be OK.
	Object pk;
	try {
	    pk = ejbObject.getPrimaryKey();
	} catch(RemoteException e) {
	    TraceEjb.error("putEJBObject: cannot get pk");
	    return;
	}
	if (!ejbobjects.containsKey(pk)) {
	    ejbobjects.put(pk, ejbObject);
	}
    }


    /**
     * Returns the EJBObject to which the specified primary key is mapped
     * The callers must be synchronized
     *
     * @param  pk - the primary key 
     * @return the value to which the key is mapped in this hashtable; 
     *         null if the key is not mapped to any value in this hashtable.
     */
    public JBeanEntity getEJBObject(Object pk) {

	if (TraceEjb.isDebug) TraceEjb.debug("JEntityHome.getEJBObject " +pk);

	JBeanEntity bb = null;
	bb = (JBeanEntity) ejbobjects.get(pk);
	if (bb == null) {
	    if (TraceEjb.isDebug) 
		TraceEjb.debug("There is no EJBObject for this primary key " +pk);
	}
	// Do not check isMarkedRemoved() anymore: not really useful and it
	// leads to memory leaks
	return bb;
    }

    /**
     * Remove EJBObject from the list
     * @args pk the Primary Key of this EJBObject
     */
    public void removeEJBObject(Object pk) {

	if (pk == null) {
	    TraceEjb.verbose("JEntityHome.removeEJBObject: pk = null");
	    return;
	}
	TraceEjb.debug("JEntityHome.removeEJBObject");

	try {
	    synchronized(ejbobjects) {
		ejbobjects.remove(pk);
	    }
	} catch (Exception e) {
	    TraceEjb.error("Could not remove EJBObject: ", e);
	}
    }


    /**
     * Returns the DataSource associated to the bean
     * JOnAS dependant, used mainly by generated classes.
     *
     * @return  the DataSource associated to the bean or null if there is no associated DataSource
     *
     */
    public Object getDataSource() {
	TraceEjb.debug("JEntityHome.getDataSource");
	if (datasource == null) {
	    TraceEjb.error("JOnAS configuration error:");
	    TraceEjb.error("Datasource is null in EntityHome");
	}
	return datasource;
    }


    /**
     * Create a new couple Context+Instance
     * @params ejbobject The ejbObject owning this context
     * @params tx The transaction associated to it
     *
     * @returns ctx The new Entity Context
     */
    public JContextEntity createEntityContext(JBeanEntity ejbo, Transaction tx) throws RemoteException {
	TraceEjb.debug("JEntityHome.createEntityContext");

	JContextEntity ctx = null;
	EntityBean eb = null;
	try {
	    eb = createNewInstance();
	    ctx = new JContextEntity(this, eb);
	} catch (Exception e) {
	    TraceEjb.error("createEntityContext: Failed to create new instance:", e);
	    throw new RemoteException("Container cannot create a new instance", e);
	}
	// Init the Context and give it to the instance.
	ctx.initEntityContext(ejbo, tx);
	eb.setEntityContext(ctx);
	return ctx;
    }

    /**
     * Get a pseudo instance of the bean for finder methods
     * This instance is never destroyed and can be reused.
     */
    public synchronized EntityBean getAvailable() throws RemoteException {

	if (available == null) {
	    available = createEntityContext(null, null);
	}
	return available.getInstance();
    }

    /**
     * preinvoke for entity home methods
     * Entity beans must be container managed for transations,
     * so we needn't to test this.
     */
    protected RequestCtx preinvoke(int txattr, String methodSignature) throws RemoteException {

	TraceEjb.debug("JEntityHome.preinvoke");

	// security controls
	checkSecurity(methodSignature);

	// Build a RequestCtx to save information about this request
	// until the postinvoke time.
	RequestCtx rctx = new RequestCtx(txattr);

	// Set bean context for JNDI
	rctx.setOldJNDICtx(setComponentContext());

	// Process transaction attribute
	container.checkTransactionIn(rctx);

	return rctx;
    }

    /**
     * postinvoke for Entity home methods
     */
    public void postinvoke(RequestCtx rctx) throws RemoteException {
	TraceEjb.debug("JEntityHome.postinvoke");
        
        // Check reference to EJBContext
	EJBContext ctx = rctx.getEJBContext();
	if (ctx == null) {
	    TraceEjb.error("null EJBContext in RequestCtx!");
	}

	// Commit or Rollback transaction started in preinvoke
	if (rctx.mustCommit()) {
	    if (rctx.getSysExc() != null) {
		// rollback tx
		try {
		    TraceEjb.error("System exc. => Rollback the transaction.");
		    tm.rollback();
		} catch (Exception e) {
		    TraceEjb.error("Exception during rollback:", e);
		}
            } else {
	        // try to commit the transaction, even in case of appl. Exc.
		// If rollbackonly, do not throw any runtime exception to the client.
		boolean rollb = ctx.getRollbackOnly();
		try {
		    if (rollb) {
			TraceEjb.verbose("postinvoke: Rolling back transaction");
			tm.rollback();
		    } else {
			TraceEjb.debugInvoke("postinvoke: Committing transaction");
			tm.commit();
		    }
		} catch (RollbackException e) {
		    // Probably a pb while writing data on database.
		    throw new RemoteException("Container cannot commit a transaction", e);
		} catch (Exception e) {
		    TraceEjb.error("postInvoke: ", e);
		    throw new RemoteException("Container exception", e);
		}
	    }
	}

	// We got a system Exception in a method from home:
	// - Log system Exception
	// - discard instance (???)
	// - set client transaction rollback only
	// - throw remote exception
	if (rctx.getSysExc() != null) {
	    // Log system exception
	    TraceEjb.error("postinvoke: System Exception in a home method:"+rctx.getSysExc());

	    // If client transaction: set it rollback only and throw TransactionRolledBackException
	    Transaction currentTx = null;
	    try {
		currentTx = tm.getTransaction();
		if (currentTx != null) {
		    currentTx.setRollbackOnly();
		    throw new TransactionRolledbackException("System exception in business method");
		}
	    } catch (SystemException e) {
		TraceEjb.error("postinvoke: cannot set rollback only current tx:", e);
	    }
	    return;
	}
	
	// Reset bean context for JNDI
	naming.setComponentContext(rctx.getOldJNDICtx());

	// Resume transaction context of the caller
	Transaction cltx = rctx.getClientTx();
	if (cltx != null) {
	    try {
		tm.resume(cltx);
	    } catch (SystemException e) {
		TraceEjb.error("postinvoke: cannot resume tx:\n", e);
	    }
	}

	// If no transaction, we must release the EntityContext
	if (rctx.getCurrentTx() == null) {
	    JContextEntity ec = (JContextEntity) rctx.getEJBContext();
	    if (ec != null) {	// No entity context for finder methods!
		JBeanEntity ejbobject = null;
		try {
		    ejbobject = (JBeanEntity) ec.getEJBObject();
		    if (ejbobject == null) {
			TraceEjb.error("postinvoke: ejbobject == null");
		    } else {
			ejbobject.releaseContext();
		    }
		} catch (IllegalStateException e) {
		    // nothing to do if object deleted.
		}
	    }
	}
    }

    /**
     * Passivate all instances without transaction
     */
    public void passivateAllIH() {
	TraceEjb.debug("JEntityHome.passivateAllIH");

	JBeanEntity ejb = null;
	Enumeration enum = null;
	synchronized(ejbobjects) {
	    enum = ejbobjects.elements();
	}
	while (enum.hasMoreElements()) {
	    ejb = (JBeanEntity) enum.nextElement();
	    ejb.passivateIH();
	}
    }

    /**
     * Return true if bean is reentrant
     */
    public boolean isReentrant() {
	return isReentrant;
    }

    /* 
     * must be generated in the interposition class
     */
    protected abstract EntityBean createNewInstance();

}
