All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.ejb.containers.AbstractSingletonContainer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
 * Copyright 2021 Contributors to the Eclipse Foundation
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package com.sun.ejb.containers;

import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import jakarta.ejb.CreateException;
import jakarta.ejb.EJBException;
import jakarta.ejb.NoSuchEJBException;
import jakarta.ejb.NoSuchObjectLocalException;
import jakarta.ejb.RemoveException;

import com.sun.ejb.ComponentContext;
import com.sun.ejb.Container;
import com.sun.ejb.EjbInvocation;
import com.sun.ejb.InvocationInfo;
import com.sun.ejb.containers.util.pool.ObjectFactory;
import com.sun.ejb.monitoring.stats.EjbMonitoringStatsProvider;
import com.sun.ejb.monitoring.stats.SingletonBeanStatsProvider;
import com.sun.enterprise.admin.monitor.callflow.ComponentType;
import com.sun.enterprise.deployment.LifecycleCallbackDescriptor;
import com.sun.enterprise.deployment.LifecycleCallbackDescriptor.CallbackType;
import com.sun.enterprise.deployment.MethodDescriptor;
import com.sun.enterprise.security.SecurityManager;
import com.sun.enterprise.util.Utility;
import org.glassfish.api.invocation.ComponentInvocation;
import org.glassfish.ejb.deployment.descriptor.EjbDescriptor;
import org.glassfish.ejb.startup.SingletonLifeCycleManager;


public abstract class AbstractSingletonContainer
    extends BaseContainer {

    private static final byte[] singletonInstanceKey = {0, 0, 0, 1};

    // All Singleton EJBs have the same instanceKey
    //  Note: the first byte of instanceKey must be left empty.

    // Note : Singletons do not support the legacy EJB 2.x RemoteHome/LocalHome
    //        client views.


    private EJBLocalObjectImpl theEJBLocalBusinessObjectImpl = null;
    private EJBLocalObjectImpl theOptionalEJBLocalBusinessObjectImpl = null;


    // Data members for Remote business view. Any objects representing the
    // Remote business interface are not subtypes of EJBObject.
    private EJBObjectImpl theRemoteBusinessObjectImpl = null;

    // Information about a web service ejb endpoint.  Used as a conduit
    // between webservice runtime and ejb container.  Contains a Remote
    // servant used by jaxrpc to call web service business method.
    //TODO private EjbRuntimeEndpointInfo webServiceEndpoint;

    protected ObjectFactory singletonCtxFactory;

    private SingletonLifeCycleManager lcm;

    protected AtomicBoolean singletonInitialized = new AtomicBoolean(false);

    // used to protect against synchronous loopback calls during Singleton init
    private boolean initializationInProgress = false;

    // Set to true if Singleton failed to complete its initialization successfully.
    // If true, Singleton is not accessible.
    protected boolean singletonInitializationFailed = false;

    protected volatile ComponentContext singletonCtx;


    private InvocationInfo postConstructInvInfo;
    private InvocationInfo preDestroyInvInfo;

    /**
     * This constructor is called from the JarManager when a Jar is deployed.
     * @exception Exception on error
     */

    protected AbstractSingletonContainer(EjbDescriptor desc, ClassLoader loader, SecurityManager sm)
        throws Exception {
        super(ContainerType.SINGLETON, desc, loader, sm);

        super.createCallFlowAgent(ComponentType.SLSB);

        // Tx attribute for PostConstruct/PreDestroy methods can only be specified using
        // a PostConstruct/PreDestroy method defined on the bean class.  If nothing is
        // specified, the CMT default is for the method to run within a transaction.  We
        // actually use TX_REQUIRES_NEW to force the transaction manager to always suspend
        // any existing transaction in the case that the Singleton instance is initialized
        // lazily as a side effect of an invocation.   Like timeout methods, from the
        // developer's perspective there is never an inflowing transaction to a Singleton
        // PostConstruct or PreDestroy method.

        postConstructInvInfo = new InvocationInfo();
        postConstructInvInfo.ejbName = ejbDescriptor.getName();
        postConstructInvInfo.methodIntf = MethodDescriptor.LIFECYCLE_CALLBACK;
        postConstructInvInfo.txAttr = getTxAttrForLifecycleCallback(ejbDescriptor.getPostConstructDescriptors());;

        preDestroyInvInfo = new InvocationInfo();
        preDestroyInvInfo.ejbName = ejbDescriptor.getName();
        preDestroyInvInfo.methodIntf = MethodDescriptor.LIFECYCLE_CALLBACK;
        preDestroyInvInfo.txAttr = getTxAttrForLifecycleCallback(ejbDescriptor.getPreDestroyDescriptors());

    }

    public String getMonitorAttributeValues() {
        StringBuffer sbuf = new StringBuffer();
        sbuf.append("SINGLETON ").append(ejbDescriptor.getName());
        sbuf.append("]");

        return sbuf.toString();
    }

    @Override
    protected EjbInvocation createEjbInvocation(Object ejb, ComponentContext ctx) {
        EjbInvocation inv = super.createEjbInvocation(ejb, ctx);
        setResourceHandler(inv);

        return inv;
    }

    @Override
    protected EjbInvocation createEjbInvocation() {
        EjbInvocation inv = super.createEjbInvocation();
        setResourceHandler(inv);

        return inv;
    }

    private void setResourceHandler(EjbInvocation inv) {
        // Singletons can not store the underlying resource list
        // in the context impl since that is shared across many
        // concurrent invocations.  Instead, set the resource
        // handler on the invocation to provide a different
        // resource List for each Singleton invocation.
        inv.setResourceHandler(SimpleEjbResourceHandlerImpl.getResourceHandler(transactionManager));
    }

    protected void initializeHome()
        throws Exception
    {
        super.initializeHome();

        if ( isRemote ) {
            if( hasRemoteBusinessView ) {

                theRemoteBusinessObjectImpl =
                    instantiateRemoteBusinessObjectImpl();

                for(RemoteBusinessIntfInfo next :
                        remoteBusinessIntfInfo.values()) {
                    java.rmi.Remote stub = next.referenceFactory.
                        createRemoteReference(singletonInstanceKey);
                    theRemoteBusinessObjectImpl.setStub
                        (next.generatedRemoteIntf.getName(), stub);
                }
            }
        }

        if ( isLocal ) {

            if( hasLocalBusinessView ) {
                theEJBLocalBusinessObjectImpl =
                    instantiateEJBLocalBusinessObjectImpl();
            }
            if (hasOptionalLocalBusinessView) {
                theOptionalEJBLocalBusinessObjectImpl =
                    instantiateOptionalEJBLocalBusinessObjectImpl();
            }
        }

        createBeanPool();
        registerMonitorableComponents();
    }

    private void createBeanPool() {
        this.singletonCtxFactory = new SingletonContextFactory();
    }

    private int getTxAttrForLifecycleCallback(
             Set lifecycleCallbackDescriptors) throws Exception {
        return getTxAttrForLifecycleCallback(lifecycleCallbackDescriptors,
                Container.TX_REQUIRES_NEW, Container.TX_NOT_SUPPORTED);
    }

    protected void registerMonitorableComponents() {
        super.registerMonitorableComponents();
        _logger.log(Level.FINE, "[Singleton Container] registered monitorable");
    }

    protected EjbMonitoringStatsProvider getMonitoringStatsProvider(
            String appName, String modName, String ejbName) {
        // TODO - which stats provider to use?
        return new SingletonBeanStatsProvider(getContainerId(), appName, modName, ejbName);
    }

    public void onReady() {
    }

    public EJBObjectImpl createRemoteBusinessObjectImpl()
        throws CreateException, RemoteException
    {
        // No access check since this is an internal operation.

        ejbProbeNotifier.ejbBeanCreatedEvent(getContainerId(),
                containerInfo.appName, containerInfo.modName,
                containerInfo.ejbName);

        return theRemoteBusinessObjectImpl;
    }


    /**
     *
     */
    public EJBObjectImpl createEJBObjectImpl()
        throws CreateException, RemoteException
    {
        throw new CreateException("EJB 2.x Remote view not supported on Singletons");
    }

    /**
     * Called during client creation request through EJB LocalHome view.
     */
    public EJBLocalObjectImpl createEJBLocalObjectImpl()
        throws CreateException
    {
        throw new CreateException("EJB 2.x Local view not supported on Singletons");
    }

    /**
     * Called during internal creation of session bean
     */
    public EJBLocalObjectImpl createEJBLocalBusinessObjectImpl(boolean localBeanView)
        throws CreateException
    {
        // No access checks needed because this is called as a result
        // of an internal creation, not a user-visible create method.
        return (localBeanView)
                ? theOptionalEJBLocalBusinessObjectImpl
                : theEJBLocalBusinessObjectImpl;
    }


    // Doesn't apply to Singletons
    protected void removeBean(EJBLocalRemoteObject ejbo, Method removeMethod,
        boolean local)
    throws RemoveException, EJBException, RemoteException
    {
        throw new EJBException("Not applicable to Singletons");
    }

    /**
     * Force destroy the EJB should be a no-op for singletons.
     * After Initialization completes successfully, runtime exceptions
     * during invocations on the Singleton do not result in the instance
     * being destroyed.
     */
    protected void forceDestroyBean(EJBContextImpl sc) {
    }


    /**
     * Not applicable to Singletons
     */
    protected EJBObjectImpl getEJBObjectImpl(byte[] instanceKey) {
        return null;
    }

    EJBObjectImpl getEJBRemoteBusinessObjectImpl(byte[] instanceKey) {
        return theRemoteBusinessObjectImpl;
    }

    /**
     * Not applicable to Singletons
     */
    protected EJBLocalObjectImpl getEJBLocalObjectImpl(Object key) {
        return null;
    }

    /**
    * Called from EJBLocalObjectImpl.getLocalObject() while deserializing
    * a local business object reference.
    */
    EJBLocalObjectImpl getEJBLocalBusinessObjectImpl(Object key) {
        return theEJBLocalBusinessObjectImpl;
    }

    /**
    * Called from EJBLocalObjectImpl.getLocalObject() while deserializing
    * a local business object reference.
    */
    EJBLocalObjectImpl getOptionalEJBLocalBusinessObjectImpl(Object key) {
        return theOptionalEJBLocalBusinessObjectImpl;
    }


    public void setSingletonLifeCycleManager(SingletonLifeCycleManager lcm) {
        this.lcm = lcm;
    }

    protected void checkInit() {
        if( singletonInitializationFailed ) {
            throw new NoSuchEJBException("Singleton " + ejbDescriptor.getName() + " is unavailable " +
                                   "because its original initialization failed.");
        }

        if (! singletonInitialized.get()) {
            //Note: NEVER call instantiateSingletonInstance() directly from here
            // The following starts all dependent beans as well
            //
            //Also, it is OK to call the following by concurrent threads
            lcm.initializeSingleton(this);
        }
    }

    // Called from SingletonLifeCycleManager to initialize a Singleton as part of the
    // eager loading and @DependsOn sequence
    public ComponentContext instantiateSingletonInstance() {
        if (! singletonInitialized.get()) {
            synchronized (this) {
                if (! singletonInitialized.get()) {

                    // All other locks must be grabbed first.  This check prevents
                    // synchronous loopback attempts during Singleton PostConstruct
                    if( initializationInProgress ) {
                        throw new EJBException("Illegal synchronous loopback call during Singleton " +
                            ejbDescriptor.getName() + " initialization would have resulted in deadlock");

                    }

                    initializationInProgress = true;

                    ClassLoader originalCCL = null;
                    try {
                        // This may be happening on the base container initialization thread
                        // rather than on an invocation thread so set the CCL
                        originalCCL = Utility.setContextClassLoader(loader);

                        //The following may throw exception
                        singletonCtx = (ComponentContext) singletonCtxFactory.create(null);
                        //this allows _getContext() to proceed
                        singletonInitialized.set(true);
                    } finally {
                        if( originalCCL != null ) {
                            Utility.setContextClassLoader(originalCCL);
                        }
                    }
                }
            }
        }

        return singletonCtx;
    }

    @Override
    protected EJBContextImpl _constructEJBContextImpl(Object instance) {
    return new SingletonContextImpl(instance, this);
    }

    private SingletonContextImpl createSingletonEJB()
        throws CreateException
    {
        EjbInvocation ejbInv = null;
        SingletonContextImpl context;

        // Track whether initialization got as far as preInvokeTx.
        // Needed for adequate error handling in the face of an initialization
        // exception.
        boolean initGotToPreInvokeTx = false;

        try {

            // a dummy invocation will be created by the BaseContainer to support
            // possible AroundConstruct interceptors
            context = (SingletonContextImpl) createEjbInstanceAndContext();

            Object ejb = context.getEJB();

            // this allows JNDI lookups from setSessionContext, ejbCreate
            ejbInv = createEjbInvocation(ejb, context);
            invocationManager.preInvoke(ejbInv);

            // Perform injection right after where setSessionContext
            // would be called.  This is important since injection methods
            // have the same "operations allowed" permissions as
            // setSessionContext.
            injectEjbInstance(context);

            if ( isRemote ) {


                if( hasRemoteBusinessView ) {
                    context.setEJBRemoteBusinessObjectImpl
                        (theRemoteBusinessObjectImpl);
                }

            }
            if ( isLocal ) {

                if( hasLocalBusinessView ) {
                    context.setEJBLocalBusinessObjectImpl
                        (theEJBLocalBusinessObjectImpl);
                }
                if( hasOptionalLocalBusinessView ) {
                    context.setOptionalEJBLocalBusinessObjectImpl
                        (theOptionalEJBLocalBusinessObjectImpl);
                }
            }

            // Call preInvokeTx directly.  InvocationInfo containing tx
            // attribute must be set prior to calling preInvoke
            ejbInv.transactionAttribute = postConstructInvInfo.txAttr;
            ejbInv.invocationInfo = postConstructInvInfo;
            initGotToPreInvokeTx = true;
            preInvokeTx(ejbInv);

            context.setInstanceKey(singletonInstanceKey);

            intercept(CallbackType.POST_CONSTRUCT, context);


        } catch ( Throwable th ) {
            if (ejbInv != null) {
                ejbInv.exception = th;
            }
            singletonInitializationFailed = true;
            CreateException creEx = new CreateException("Initialization failed for Singleton " +
                                    ejbDescriptor.getName());
            creEx.initCause(th);
            throw creEx;
        } finally {
            initializationInProgress = false;
            if (ejbInv != null) {
                try {
                    invocationManager.postInvoke(ejbInv);
                    if( initGotToPreInvokeTx ) {
                        postInvokeTx(ejbInv);
                    }
                } catch(Exception pie) {
                    if (ejbInv.exception != null) {
                        _logger.log(Level.WARNING, "Exception during Singleton startup postInvoke ", pie);
                    } else {
                        ejbInv.exception = pie;
                        singletonInitializationFailed = true;
                        CreateException creEx = new CreateException("Initialization failed for Singleton " +
                                        ejbDescriptor.getName());
                        creEx.initCause(pie);
                        throw creEx;
                    }
                }
            }
        }

        // Set the state to POOLED after ejbCreate so that
        // EJBContext methods not allowed will throw exceptions
        context.setState(EJBContextImpl.BeanState.POOLED);
        context.touch();
        return context;
    }

    protected void doTimerInvocationInit(EjbInvocation inv, Object primaryKey)
            throws Exception {
        if( isRemote ) {

            // @@@ Revisit setting ejbObject in invocation.
            // What about if bean doesn't expose a remote or local view?
            // How is inv.ejbObject used in timer invocation ?

            //TODO inv.ejbObject = theEJBObjectImpl;
            inv.isLocal = false;
        } else {
            // inv.ejbObject =
            inv.isLocal = true;
        }
    }

    public boolean userTransactionMethodsAllowed(ComponentInvocation inv) {
        boolean utMethodsAllowed = false;
        if( isBeanManagedTran ) {
            if( inv instanceof EjbInvocation ) {

                EjbInvocation ejbInv = (EjbInvocation) inv;
                AbstractSessionContextImpl sc = (AbstractSessionContextImpl) ejbInv.context;

                // Allowed any time after dependency injection
                utMethodsAllowed = (sc.getInstanceKey() != null);
            }
        }
        return utMethodsAllowed;
    }


    /**
    * Check if the given EJBObject/LocalObject has been removed.
    * @exception NoSuchObjectLocalException if the object has been removed.
    */
    protected void checkExists(EJBLocalRemoteObject ejbObj)
    {
        // Doesn't apply to Singletons
    }

    protected void afterBegin(EJBContextImpl context) {
        // Singleton SessionBeans cannot implement SessionSynchronization!!
        // EJB2.0 Spec 7.8.
    }

    protected void beforeCompletion(EJBContextImpl context) {
        // Singleton SessionBeans cannot implement SessionSynchronization!!
        // EJB2.0 Spec 7.8.
    }

    protected void afterCompletion(EJBContextImpl ctx, int status) {
        // Singleton SessionBeans cannot implement SessionSynchronization!!
    }

    // default
    public boolean passivateEJB(ComponentContext context) {
        return false;
    }

    // default
    public void activateEJB(Object ctx, Object instanceKey) {}

/** TODO
    public void appendStats(StringBuffer sbuf) {
    sbuf.append("\nSingletonContainer: ")
        .append("CreateCount=").append(statCreateCount).append("; ")
        .append("RemoveCount=").append(statRemoveCount).append("; ")
        .append("]");
    }
**/

    protected void doConcreteContainerShutdown(boolean appBeingUndeployed) {

        ClassLoader originalCCL = null;

        try {

            originalCCL = Utility.setContextClassLoader(loader);

            // Shutdown the singleton instance if it hasn't already been shutdown.
            if (singletonCtxFactory != null) {
                singletonCtxFactory.destroy(singletonCtx);
            }

            /*TODO
            if( isWebServiceEndpoint && (webServiceEndpoint != null) ) {
                String endpointAddress =
                    webServiceEndpoint.getEndpointAddressUri();
                WebServiceEjbEndpointRegistry.getRegistry().
                    unregisterEjbWebServiceEndpoint(endpointAddress);
            }

            EjbBundleDescriptor desc = ejbDescriptor.getEjbBundleDescriptor();
            if (desc != null && desc.getServiceReferenceDescriptors()!= null) {
                for (Object srd : desc.getServiceReferenceDescriptors()) {
                    ClientPipeCloser.getInstance()
                    .cleanupClientPipe((ServiceReferenceDescriptor)srd);
                }
            }
            */



            if ( hasRemoteBusinessView ) {
                for(RemoteBusinessIntfInfo next :
                        remoteBusinessIntfInfo.values()) {
                    next.referenceFactory.destroyReference
                        (theRemoteBusinessObjectImpl.getStub
                            (next.generatedRemoteIntf.getName()),
                         theRemoteBusinessObjectImpl.getEJBObject
                            (next.generatedRemoteIntf.getName()));
                }
            }


        } catch(Throwable t) {

            _logger.log(Level.FINE, "Exception during Singleton shutdown", t);

        } finally {

            singletonCtxFactory = null;
            Utility.setContextClassLoader(originalCCL);
        }
    }

    public long getMethodReadyCount() {
    return 0; //TODO FIXME
    }

    protected class SingletonContextFactory
        implements ObjectFactory
    {

        public Object create(Object param) {
            try {
                    return createSingletonEJB();
            } catch (CreateException ex) {
                    throw new EJBException(ex);
            }
        }

        public void destroy(Object obj) {
            SingletonContextImpl singletonCtx = (SingletonContextImpl) obj;
            // Note: Singletons cannot have incomplete transactions
            // in progress. So it is ok to destroy the EJB.

            // Only need to cleanup and destroy bean (andd call @PreDestroy)
            // if it successfully completed initialization.
            if ( singletonCtx != null )  {

                Object sb = singletonCtx.getEJB();

                // Called from pool implementation to reduce the pool size.
                // So we need to call @PreDestroy and mark context as destroyed

                singletonCtx.setState(EJBContextImpl.BeanState.DESTROYED);
                EjbInvocation ejbInv = null;
                try {
                    // NOTE : Context class-loader is already set by Pool
                    ejbInv = createEjbInvocation(sb, singletonCtx);
                    invocationManager.preInvoke(ejbInv);
                    singletonCtx.setInEjbRemove(true);

                    // Call preInvokeTx directly.  InvocationInfo containing tx
                    // attribute must be set prior to calling preInvoke
                    ejbInv.transactionAttribute = preDestroyInvInfo.txAttr;
                    ejbInv.invocationInfo = preDestroyInvInfo;
                    preInvokeTx(ejbInv);

                    intercept(CallbackType.PRE_DESTROY, singletonCtx);

                } catch ( Throwable t ) {
                    if( ejbInv != null ) {
                        ejbInv.exception = t;
                    }
                    _logger.log(Level.FINE, "ejbRemove exception", t);
                } finally {
                    singletonCtx.setInEjbRemove(false);
                    if( ejbInv != null ) {
                        invocationManager.postInvoke(ejbInv);
                        try {
                            postInvokeTx(ejbInv);
                        } catch(Exception pie) {
                            _logger.log(Level.FINE, "singleton postInvokeTx exception", pie);
                        }
                    }
                }

                cleanupInstance(singletonCtx);

                singletonCtx.deleteAllReferences();
            }
        }

    } // SessionContextFactory{}


    //Methods for StatelessSessionBeanStatsProvider
    public int getMaxPoolSize() {
        return 1;
    }

    public int getSteadyPoolSize() {
        return 1;
    }

    //The inner class ResourceHandlerImpl has been moved into its own (top level) class called
    // SimpleEjbResourceHandlerImpl because it is now used by EjbInvocation.clone() method also.
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy