/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2005 Bull S.A.
 * Contact: jonas-team@ow2.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: JMdbEndpointFactory.java 15428 2008-10-07 11:20:29Z sauthieg $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.lib.ejb21;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import javax.ejb.EJBException;
import javax.ejb.MessageDrivenBean;
import javax.ejb.MessageDrivenContext;
import javax.ejb.Timer;
import javax.ejb.TimerService;
import javax.jms.Destination;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.ResourceAdapter;
import javax.resource.spi.UnavailableException;
import javax.resource.spi.endpoint.MessageEndpoint;
import javax.resource.spi.endpoint.MessageEndpointFactory;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.xa.XAResource;

import org.ow2.jonas.deployment.ejb.ActivationConfigPropertyDesc;
import org.ow2.jonas.deployment.ejb.MessageDrivenDesc;
import org.ow2.jonas.deployment.ejb.MethodDesc;
import org.ow2.jonas.lib.execution.ExecutionResult;
import org.ow2.jonas.lib.execution.IExecution;
import org.ow2.jonas.lib.execution.RunnableHelper;
import org.ow2.jonas.resource.Rar;
import org.ow2.jonas.resource.ResourceService;

import org.objectweb.util.monolog.api.BasicLevel;

/**
 * This class is a factory for a Message Driven Bean Endpoints There is one such
 * class per MDB class. Contains all information related to the bean
 * @author Eric Hardesty
 */
public class JMdbEndpointFactory extends JFactory implements MessageEndpointFactory {

    /**
     * pool of JMessageEndpoint objects
     */
    private List endpool = new ArrayList();

    /**
     * count of the instances
     */
    private int instanceCount = 0;

    /**
     *  minimum pool size
     */
    private int minPoolSize = 0;

    /**
     *  maximum number of instances in the cache
     */
    private int maxCacheSize = 0;

    /**
     * ActivationSpec associated with this factory
     */
    private ActivationSpec as = null;

    /**
     * Message listener type
     */
    private String msglistenType = null;

    /**
     * activated indicator
     */
    private boolean activated = false;

    /**
     * Resource Adapter
     */
    ResourceAdapter resadp = null;

    /**
     * Constructor
     * @param dd Message Driven Descriptor
     * @param cont Container where this bean is defined
     * @param as ActivationSpec to link the Container to
     */
    public JMdbEndpointFactory(MessageDrivenDesc dd, JContainer cont, ActivationSpec as, ResourceService rserv) {
        super(dd, cont);
        String dest = dd.getDestinationJndiName();
        // Set the AS properties and validate the required config properties
        List acpl = null;
        if (dd.getMdActivationConfigDesc() != null) {
            acpl = dd.getMdActivationConfigDesc().getActivationConfigPropertyList();
        }
        List jAcpl = null;
        if (dd.getJonasMdActivationConfigDesc() != null) {
            jAcpl = dd.getJonasMdActivationConfigDesc().getActivationConfigPropertyList();
        }
        mdbEFInit(dd, dest, acpl, jAcpl, as, rserv);
    }

    /**
     * Constructor
     * @param dd Message Driven Descriptor
     * @param destination String of desired destination
     * @param cont Container where this bean is defined
     * @param as ActivationSpec to link the Container to
     */
    public JMdbEndpointFactory(MessageDrivenDesc dd, String destination, JContainer cont,
                               ActivationSpec as, ResourceService rserv) {
        super(dd, cont);

        // Build Activation spec linked list from variables

        List jAcpl = new LinkedList();
        List acpl = null;
        if (dd.getMdActivationConfigDesc() != null) {
            acpl = dd.getMdActivationConfigDesc().getActivationConfigPropertyList();
        }
        buildACL(dd, jAcpl);

        mdbEFInit(dd, destination, acpl, jAcpl, as, rserv);
    }

    /**
     * Create the ActivationConfigProperty List
     * @param dd MessageDrivenDesc
     * @param jacl List to update
     */
    private void buildACL(MessageDrivenDesc dd, List jacl) {

        String [] aclNames = {"destination", "destinationType", "messageSelector",
                              "acknowledgeMode", "subscriptionDurability" };

        ActivationConfigPropertyDesc acp = null;

        acp = new ActivationConfigPropertyDesc();
        acp.setActivationConfigPropertyName("destination");
        acp.setActivationConfigPropertyValue(dd.getDestinationJndiName());
        jacl.add(acp);

        acp = new ActivationConfigPropertyDesc();
        acp.setActivationConfigPropertyName("destinationType");
        if (dd.isTopicDestination()) {
            acp.setActivationConfigPropertyValue("javax.jms.Topic");
        } else {
            acp.setActivationConfigPropertyValue("javax.jms.Queue");
        }
        jacl.add(acp);

        acp = new ActivationConfigPropertyDesc();
        acp.setActivationConfigPropertyName("messageSelector");
        acp.setActivationConfigPropertyValue(dd.getSelector());
        jacl.add(acp);

        acp = new ActivationConfigPropertyDesc();
        acp.setActivationConfigPropertyName("acknowledgeMode");
        if (dd.getAcknowledgeMode() == MessageDrivenDesc.AUTO_ACKNOWLEDGE) {
            acp.setActivationConfigPropertyValue("Auto-acknowledge");
        } else {
            acp.setActivationConfigPropertyValue("Dups-ok-acknowledge");
        }
        jacl.add(acp);

        acp = new ActivationConfigPropertyDesc();
        acp.setActivationConfigPropertyName("subscriptionDurability");
        if (dd.getSubscriptionDurability() == MessageDrivenDesc.SUBS_DURABLE) {
            acp.setActivationConfigPropertyValue("Durable");
        } else {
            acp.setActivationConfigPropertyValue("NonDurable");
        }
        jacl.add(acp);

        if (dd.getSubscriptionDurability() == MessageDrivenDesc.SUBS_DURABLE) {
            acp = new ActivationConfigPropertyDesc();
            acp.setActivationConfigPropertyName("subscriptionName");
            acp.setActivationConfigPropertyValue(dd.getEjbName());
            jacl.add(acp);
        }

        // Check existing list to determine if additional items are configured, then pass them along also
        List acpl = null;
        if (dd.getJonasMdActivationConfigDesc() != null) {
            acpl = dd.getJonasMdActivationConfigDesc().getActivationConfigPropertyList();

            String acpName = null;
            boolean found = false;
            for (int i = 0; i < acpl.size(); i++) {
                acp = (ActivationConfigPropertyDesc) acpl.get(i);
                acpName = acp.getActivationConfigPropertyName();
                found = false;
                for (int j = 0; j < aclNames.length; j++) {
                    if (acpName.equals(aclNames[j])) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    jacl.add(acp);
                }
            }

        }
    }

    /**
     * Init this endpointfactory
     * @param dd Message Driven Descriptor
     * @param dest String destination desired
     * @param acpl List of activation properties
     * @param jAcpl List of JOnAS activation properties
     * @param aSpec ActivationSpec to link the Container to
     */
    private void mdbEFInit(MessageDrivenDesc dd, String dest, List acpl,
                           List jAcpl, ActivationSpec aSpec, ResourceService ressv) {

        String methName = "mdbEFInit: ";
        String ejbName = dd.getEjbName();

        // Check if tx managed by the bean or the container
        txbeanmanaged = dd.isBeanManagedTransaction();

        // Set ActivationSpec
        as = aSpec;

        minPoolSize = dd.getPoolMin();
        maxCacheSize = dd.getCacheMax();
        if (TraceEjb.isDebugJms()) {
            TraceEjb.mdb.log(BasicLevel.DEBUG, methName + "maxCacheSize = " + maxCacheSize + " minPoolSize = " + minPoolSize);
        }

        // Get the Rar by its jndi name
        Rar rar = null;
        if (ressv != null) {
            rar = ressv.getRar(dest);
        } else {
            TraceEjb.mdb.log(BasicLevel.ERROR, "ResourceService ref missing");
            throw new EJBException("No resource service");
        }
        // Get the messagelistenerType
        msglistenType = rar.getInterface(dest);

        try {
            rar.configureAS(as, acpl, jAcpl, dest, ejbName);
        } catch (Exception ex) {
            TraceEjb.mdb.log(BasicLevel.ERROR, methName + "cannot configure activationspec " + ex);
            ex.printStackTrace();
            throw new EJBException("cannot configure activationspec ", ex);
        }
        resadp = rar.getResourceAdapter();

        // pre-allocate a set of Endpoints
        synchronized (endpool) {
            for (int i = 0; i < minPoolSize; i++) {
                JMessageEndpoint ep = null;
                ep = createNewInstance();
                endpool.add(ep);
            }
        }
        if (minPoolSize != 0) {
            TraceEjb.mdb.log(BasicLevel.INFO, methName + "pre-allocate a set of " + minPoolSize
                                 + " message driven bean  instances");
        }

        // Call to register an XAResource with JOTM
        try {
            ActivationSpec [] asArray = new ActivationSpec[1];
            asArray[0] = as;
            XAResource [] xar = resadp.getXAResources(asArray);
            if (xar != null && xar.length > 0) {
                tm.registerResourceManager(ejbName + msglistenType, xar[0], "", null, null);
           }
        } catch (Exception ex) {
            TraceEjb.mdb.log(BasicLevel.ERROR, ex.getMessage(), ex);
        }

        // EndpointActivation
        final JMdbEndpointFactory fact = this;
        IExecution<Void> activator = new IExecution<Void>() {
            public Void execute() throws Exception {
        	resadp.endpointActivation(fact, as);
        	return (Void) null;
            }
        };
        ExecutionResult<Void> result = RunnableHelper.execute(getClass().getClassLoader(), activator);
        // Throw an ServiceException if needed
        if (result.hasException()) {
            activated = false;
            TraceEjb.mdb.log(BasicLevel.ERROR, methName + "cannot activate endpoint ");
            throw new EJBException("cannot activate endpoint ", result.getException());
        }
        activated = true;
    }

    // ---------------------------------------------------------------
    // Specific BeanFactory implementation
    // ---------------------------------------------------------------

    /**
     * Init pool of instances
     */
    public void initInstancePool() {
    }

    /**
     * @return the size of the EndpointPool
     */
    public int getPoolSize() {
        return endpool.size();
    }

    /**
     * stop this EJB. call deactivate on the Endpoint Stop the threads and
     * remove the beans
     */
    public void stop() {
        String methName = "stop: ";
        if (TraceEjb.isDebugJms()) {
            TraceEjb.mdb.log(BasicLevel.DEBUG, methName);
        }
        try {
            // Desactivate the endpoint
            resadp.endpointDeactivation(this, as);

            // Loop thru the endpool and release them
            synchronized (endpool) {
                if (TraceEjb.isDebugJms()) {
                    TraceEjb.mdb.log(BasicLevel.DEBUG, methName + "stopping " + this);
                }
                JMessageEndpoint ep = null;
                while (endpool.size() > 0) {
                    ep = (JMessageEndpoint) endpool.remove(0);
                    instanceCount--;
                    try {
                        ep.mdb.ejbRemove();
                    } catch (Exception e) {
                        TraceEjb.mdb.log(BasicLevel.ERROR, methName + "Cannot remove mdb: "
                               + ep.mdb, e);
                    }
                }
            }
        } catch (Exception e) {
            TraceEjb.mdb.log(BasicLevel.WARN, methName + "Cannot deactivate the endpoint", e);
        }
        stopContainer();
    }

    /**
     * synchronize bean instances if needed
     */
    public void syncDirty(boolean notused) {
    }

    /**
     * @return the home if exist
     */
    public JHome getHome() {
        return null;
    }

    /**
     * @return the local home if exist
     */
    public JLocalHome getLocalHome() {
        return null;
    }

    // ---------------------------------------------------------------
    // MessageEndpointFactory implementation
    // ---------------------------------------------------------------

    /**
     * Create the message endpoint
     *
     * @param xaResource XAResource object to attach
     * @return MessageEndpoint to deliver messages to
     * @throws UnavailableException exception to throw
     */
    public MessageEndpoint createEndpoint(XAResource xaResource) throws UnavailableException {
        String methName = "createEndpoint: ";
        if (TraceEjb.isDebugJms()) {
            TraceEjb.mdb.log(BasicLevel.DEBUG, methName);
        }

        if (!activated) {
            TraceEjb.mdb.log(BasicLevel.ERROR, methName + "mdb is not usuable either initial deployment or undeployment ");
            throw new UnavailableException("mdb is not usuable either initial deployment or undeployment ");
        }

        JMessageEndpoint ep = null;
        try {
            ep = getNewInstance(xaResource);
        } catch (Exception ex) {
            TraceEjb.mdb.log(BasicLevel.ERROR, methName + "cannot create an endpoint ");
            throw new UnavailableException("cannot create an endpoint ", ex);
        }
        return ep.mep;
    }

    /**
     * Determine if the method is transacted
     *
     * @param method Method to check
     * @return boolean whether the specified method is transacted
     * @throws NoSuchMethodException exception to throw
     */
    public boolean isDeliveryTransacted(Method method) throws NoSuchMethodException {
        int txAttribute = 0;
        try {
            txAttribute = dd.getMethodDesc(method).getTxAttribute();
        } catch (Exception ex) {
            TraceEjb.mdb.log(BasicLevel.ERROR, "isDeliveryTransacted: No such method exists. " + method.getName(), ex);
            throw new NoSuchMethodException("No such method exists. " + method.getName());
        }
        return (txAttribute == MethodDesc.TX_REQUIRED);
    }

    // ---------------------------------------------------------------
    // Other methods
    // ---------------------------------------------------------------

    /**
     * Return an MessageEndpoint from the pool. If pool is empty, creates a new
     * one.
     * @return an MessageEndpoint from the pool.
     * @exception Exception - if an application server fails to return an
     *            MessageEndpoint out of its pool.
     */
    public JMessageEndpoint getMessageEndpoint() throws Exception {
        if (TraceEjb.isDebugJms()) {
            TraceEjb.mdb.log(BasicLevel.DEBUG, "getMessageEndpoint: ");
        }

        return getNewInstance(null);
    }

    /**
     * put the JMessageEndpoint back to the pool
     * @param ep the MessageEndpoint
     */
    public void releaseEndpoint(JMessageEndpoint ep) {
        String methName = "releaseEndpoint: ";
        if (TraceEjb.isDebugJms()) {
            TraceEjb.mdb.log(BasicLevel.DEBUG, methName + ep);
        }

        ep.setReleasedState(true);
        synchronized (endpool) {
            endpool.add(ep);
            if (TraceEjb.isDebugJms()) {
                TraceEjb.mdb.log(BasicLevel.DEBUG, methName + "notifyAll ");
            }
            endpool.notifyAll();
        }
        if (TraceEjb.isDebugJms()) {
            TraceEjb.mdb.log(BasicLevel.DEBUG, methName + "nb instances " + getCacheSize());
            TraceEjb.mdb.log(BasicLevel.DEBUG, methName + "nb free cached instances " + getPoolSize());
        }

    }

    // ---------------------------------------------------------------
    // other public methods
    // ---------------------------------------------------------------

    /**
     * Obtains the TimerService associated for this Bean
     * @return a JTimerService instance.
     */
    public TimerService getTimerService() {
        if (myTimerService == null) {
            // TODO : Check that instance implements TimedObject ?
            myTimerService = new JTimerService(this);
        }
        return myTimerService;
    }

    /**
     * @return min pool size for Jmx
     */
    public int getMinPoolSize() {
        return minPoolSize;
    }

    /**
     * @return max cache size for Jmx
     */
    public int getMaxCacheSize() {
        return maxCacheSize;
    }

    /**
     * @return current cache size ( = nb of instance created) for Jmx
     */
    public int getCacheSize() {
        return instanceCount;
    }

    /**
     * @return the Transaction Attribute
     */
    public int getTransactionAttribute() {
        return ((MessageDrivenDesc) dd).getTxAttribute();
    }

    /**
     * For Message Driven Beans, only 2 cases are possible:
     * TX_REQUIRED or TX_NOT_SUPPORTED
     * @param rctx The Request Context
     */
    public void checkTransaction(RequestCtx rctx) {
        String methName = "checkTransaction: ";
        if (rctx.txAttr == MethodDesc.TX_REQUIRED) {
            try {
                if (txbeanmanaged) {
                    if (tm.getTransaction() == null) {
                        TraceEjb.mdb.log(BasicLevel.ERROR, methName + "No transaction and need one");
                        return;
                    }
                } else {
                    if (tm.getTransaction() == null) {
                        tm.begin();
                    }
                }
                rctx.mustCommit = true;
                rctx.currTx = tm.getTransaction();
                if (TraceEjb.isDebugTx()) {
                    TraceEjb.tx.log(BasicLevel.DEBUG, "Transaction started: " + rctx.currTx);
                }
            } catch (Exception e) {
                // No exception raised in case of MDB
                TraceEjb.mdb.log(BasicLevel.ERROR, methName + "cannot start tx:", e);
                return;
            }
        } else {
            if (rctx.txAttr != MethodDesc.TX_NOT_SUPPORTED) {
                TraceEjb.mdb.log(BasicLevel.ERROR, methName + "Bad transaction attribute: " + rctx.txAttr);
            }
            try {
                rctx.currTx = tm.getTransaction();
                if (rctx.currTx != null) {
                    if (TraceEjb.isDebugJms()) {
                        TraceEjb.mdb.log(BasicLevel.DEBUG, methName + "Suspending client tx");
                    }
                    rctx.clientTx = tm.suspend();
                    rctx.currTx = null;
                }
            } catch (SystemException e) {
                TraceEjb.mdb.log(BasicLevel.ERROR, methName + "cannot suspend transaction", e);
                return;
            }
        }
    }

    /**
     * Reduce number of instances in memory in the free list we reduce to the
     * minPoolSize
     */
    public void reduceCache() {
        String methName = "reduceCache: ";
        if (TraceEjb.isDebugJms()) {
            TraceEjb.mdb.log(BasicLevel.DEBUG, methName);
        }
        // reduce the pool to the minPoolSize
        int poolsz = minPoolSize;
        synchronized (endpool) {
            if (TraceEjb.isDebugJms()) {
                TraceEjb.mdb.log(BasicLevel.DEBUG, methName + "try to reduce " + endpool.size() + " to " + poolsz);
            }
            while (endpool.size() > poolsz) {
                ListIterator i = endpool.listIterator();
                if (i.hasNext()) {
                    i.next();
                    i.remove();
                    instanceCount--;
                }
            }
        }
        if (TraceEjb.isDebugJms()) {
            TraceEjb.mdb.log(BasicLevel.DEBUG, methName + "cacheSize= " + getCacheSize());
        }

    }

    /**
     * Notify a timeout for this bean
     * @param timer timer whose expiration caused this notification.
     */
    public void notifyTimeout(Timer timer) {
        if (stopped) {
            TraceEjb.mdb.log(BasicLevel.WARN, "Container stopped");
            return;
        }
        String methName = "notifyTimeout: ";
        if (TraceEjb.isDebugJms()) {
            TraceEjb.mdb.log(BasicLevel.DEBUG, methName);
        }

        // We need an instance from the pool to process the timeout.
        JMessageEndpoint ep = null;
        try {
            ep = getNewInstance(null);
        } catch (Exception e) {
            TraceEjb.mdb.log(BasicLevel.ERROR, methName + "exception:" + e);
            throw new EJBException("Cannot deliver the timeout", e);
        }

        // deliver the timeout to the bean
        ep.deliverTimeout(timer);

        // release the instance
        releaseEndpoint(ep);
    }

    // ---------------------------------------------------------------
    // private methods
    // ---------------------------------------------------------------

    /**
     * return a new instance of the bean. Try to get one from the pool, and
     * create a new one if the pool is empty.
     *
     * @param xaResource XAResource to use
     * @return JMessageEndpoint to return
     * @throws Exception to throw
     */
    private JMessageEndpoint getNewInstance(XAResource xaResource) throws Exception {
        String methName = "getNewInstance: ";
        if (TraceEjb.isDebugJms()) {
            TraceEjb.mdb.log(BasicLevel.DEBUG, methName + "Factory: " + this + " XAResource: " + xaResource);
        }

        // try to get one from the Pool
        JMessageEndpoint ep = null;

        // try to find a free entry in the pool
        synchronized (endpool) {
            if (!endpool.isEmpty()) {
                try {
                    ep = (JMessageEndpoint) endpool.remove(0);
                } catch (Exception ex) {
                    // This should never happen
                    TraceEjb.mdb.log(BasicLevel.ERROR, methName + "Exception:" + ex);
                    throw new EJBException("Cannot get an instance from the pool", ex);
                }
            } else {
                if (TraceEjb.isDebugJms()) {
                    TraceEjb.mdb.log(BasicLevel.DEBUG, methName + "pool is empty");
                }
                if (maxCacheSize == 0 || instanceCount < maxCacheSize) {
                    // Pool has free slots, create a new MessageEndpoint object
                    ep = createNewInstance();
                } else {
                    while (endpool.isEmpty()) {
                        if (TraceEjb.isDebugJms()) {
                            TraceEjb.mdb.log(BasicLevel.DEBUG, methName + "endpool.isEmpty() = true --> wait()");
                        }
                        try {
                            endpool.wait();
                            if (TraceEjb.isDebugJms()) {
                                TraceEjb.mdb.log(BasicLevel.DEBUG, methName + "endpool notified");
                            }
                        } catch (InterruptedException e) {
                            if (TraceEjb.isDebugJms()) {
                                TraceEjb.mdb.log(BasicLevel.DEBUG, methName + "endpool waiting interrupted", e);
                            }
                        } catch (Exception e) {
                            throw new EJBException("synchronization pb", e);
                        }
                    }
                    try {
                        ep = (JMessageEndpoint) endpool.remove(0);
                    } catch (Exception ex) {
                        // This should never happen
                        TraceEjb.mdb.log(BasicLevel.ERROR, methName + "Exception:" + ex);
                        throw new EJBException("Cannot get an instance from the pool", ex);
                    }
                }

            }
            if (TraceEjb.isDebugJms()) {
                TraceEjb.mdb.log(BasicLevel.DEBUG, methName + "nb instances " + getCacheSize());
            }
            ep.setXAResource(xaResource);
            ep.setReleasedState(false);
            if (TraceEjb.isDebugJms()) {
                TraceEjb.mdb.log(BasicLevel.DEBUG, methName + "Returning " + ep);
            }
            return ep;
        }
    }

    /**
     * Create a new instance of the bean
     *
     * @return JMessageEndpoint to return
     */
    private JMessageEndpoint createNewInstance() {
        String methName = "createNewInstance: ";
        if (TraceEjb.isDebugJms()) {
            TraceEjb.mdb.log(BasicLevel.DEBUG, methName);
        }
        JMessageEndpointProxy epProxy = null;
        MessageEndpoint ep = null;
        JMessageEndpoint jep = null;
        ClassLoader cls = myClassLoader();

        // Set ContextClassLoader with the ejbclassloader.
        // This is necessary in case ejbCreate calls another bean in the same
        // jar.
        ClassLoader old = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(cls);

        // Creates the new instance
        MessageDrivenBean mdb = null;
        try {
            mdb = (MessageDrivenBean) beanclass.newInstance();
        } catch (Exception e) {
            TraceEjb.mdb.log(BasicLevel.ERROR, methName + "failed to create instance:", e);
            resetToOldClassLoader(old);
            throw new EJBException("Container failed to create instance of Message Driven Bean", e);
        }

        // Instanciates a new JMessageEndpoint object
        jep = new JMessageEndpoint(this, mdb);
        epProxy = new JMessageEndpointProxy(this, mdb, jep);
        Class msgListenerClass = null;
        try {
            msgListenerClass = cls.loadClass(msglistenType);
        } catch (ClassNotFoundException e) {
            String error = "Container failed to load class of Message Driven Bean '" + msglistenType + "'";
            TraceEjb.mdb.log(BasicLevel.ERROR, error, e);
            resetToOldClassLoader(old);
            throw new EJBException(error, e);
        }
        ep = (MessageEndpoint) Proxy.newProxyInstance(cls, new Class[] {MessageEndpoint.class, msgListenerClass}, epProxy);

        jep.setProxy(ep);
        // starts the bean instance: setMessageDrivenContext() + ejbCreate()
        // see EJB spec. 2.0 page 322.
        // Both operations must be called with the correct ComponentContext
        Context ctxsave = setComponentContext();
        mdb.setMessageDrivenContext((MessageDrivenContext) jep);
        try {
            Method m = beanclass.getMethod("ejbCreate", (Class[]) null);

            boolean bm = m.isAccessible();
            if( !bm) {
                m.setAccessible(true);
            }
            m.invoke(mdb, (Object[]) null);
            m.setAccessible(bm);
        } catch (Exception e) {
            TraceEjb.mdb.log(BasicLevel.ERROR, methName + " cannot call ejbCreate on message driven bean instance ", e);
            throw new EJBException("Container fails to call ejbCreate on message driven bean instance", e);
        } finally {
            resetToOldClassLoader(old);
            resetComponentContext(ctxsave);
        }

        synchronized (endpool) {
            instanceCount++;
        }
        return jep;
    }

    /**
     * Reset currentThread context ClassLoader to a given ClassLoader
     * @param old Old ClassLoader to reuse
     */
    private void resetToOldClassLoader(ClassLoader old) {
        if (old != null) {
            Thread.currentThread().setContextClassLoader(old);
        }
    }

    /*
     * Make sense only for entities
     */
    public void storeInstances(Transaction tx) {
        // unused

    }

}
