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

org.apache.openejb.cdi.OpenEJBLifecycle Maven / Gradle / Ivy

There is a newer version: 10.0.0-M3
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.openejb.cdi;

import org.apache.openejb.AppContext;
import org.apache.openejb.BeanContext;
import org.apache.openejb.OpenEJBRuntimeException;
import org.apache.openejb.assembler.classic.AppInfo;
import org.apache.openejb.assembler.classic.Assembler;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import org.apache.webbeans.component.BuiltInOwbBean;
import org.apache.webbeans.component.SimpleProducerFactory;
import org.apache.webbeans.component.WebBeansType;
import org.apache.webbeans.config.BeansDeployer;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.webbeans.config.WebBeansFinder;
import org.apache.webbeans.container.BeanManagerImpl;
import org.apache.webbeans.el.ELContextStore;
import org.apache.webbeans.intercept.InterceptorResolutionService;
import org.apache.webbeans.portable.AbstractProducer;
import org.apache.webbeans.portable.InjectionTargetImpl;
import org.apache.webbeans.portable.ProviderBasedProducer;
import org.apache.webbeans.portable.events.discovery.BeforeShutdownImpl;
import org.apache.webbeans.spi.ContainerLifecycle;
import org.apache.webbeans.spi.ContextsService;
import org.apache.webbeans.spi.JNDIService;
import org.apache.webbeans.spi.ResourceInjectionService;
import org.apache.webbeans.spi.ScannerService;
import org.apache.webbeans.spi.adaptor.ELAdaptor;
import org.apache.webbeans.util.WebBeansConstants;
import org.apache.webbeans.util.WebBeansUtil;

import javax.el.ELResolver;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.ConversationScoped;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.JspApplicationContext;
import javax.servlet.jsp.JspFactory;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

/**
 * @version $Rev:$ $Date:$
 */
public class OpenEJBLifecycle implements ContainerLifecycle {
    public static final ThreadLocal CURRENT_APP_INFO = new ThreadLocal();

    //Logger instance
    private static final Logger logger = Logger.getInstance(LogCategory.OPENEJB_CDI, OpenEJBLifecycle.class);

    /**
     * Discover bean classes
     */
    protected ScannerService scannerService;

    protected final ContextsService contextsService;

    /**
     * Deploy discovered beans
     */
    private final BeansDeployer deployer;

    /**
     * Using for lookup operations
     */
    private final JNDIService jndiService;

    /**
     * Root container.
     */
    private final BeanManagerImpl beanManager;
    private final WebBeansContext webBeansContext;
    /**
     * Manages unused conversations
     */

    public OpenEJBLifecycle(final WebBeansContext webBeansContext) {
        this.webBeansContext = webBeansContext;

        this.beanManager = webBeansContext.getBeanManagerImpl();
        this.deployer = new BeansDeployer(webBeansContext);
        this.jndiService = webBeansContext.getService(JNDIService.class);
        this.scannerService = webBeansContext.getScannerService();
        this.contextsService = webBeansContext.getContextsService();
    }

    @Override
    public BeanManager getBeanManager() {
        return this.beanManager;
    }

    @Override
    public void startApplication(final Object startupObject) {
        if (ServletContextEvent.class.isInstance( startupObject)) {
            startServletContext(ServletContext.class.cast(getServletContext(startupObject))); // TODO: check it is relevant
            return;
        } else if (!StartupObject.class.isInstance(startupObject)) {
            logger.debug("startupObject is not of StartupObject type; ignored");
            return;
        }

        final StartupObject stuff = (StartupObject) startupObject;
        final ClassLoader oldCl = Thread.currentThread().getContextClassLoader();

        // Initalize Application Context
        logger.info("OpenWebBeans Container is starting...");

        final long begin = System.currentTimeMillis();

        try {
            Thread.currentThread().setContextClassLoader(stuff.getClassLoader());

            final AppContext appContext = stuff.getAppContext();
            if (stuff.getWebContext() == null) { // do it before any other things to keep our singleton finder working
                appContext.setWebBeansContext(webBeansContext);
            }

            //Load all plugins
            webBeansContext.getPluginLoader().startUp();

            //Get Plugin
            final CdiPlugin cdiPlugin = (CdiPlugin) webBeansContext.getPluginLoader().getEjbPlugin();

            cdiPlugin.setClassLoader(stuff.getClassLoader());
            cdiPlugin.setWebBeansContext(webBeansContext);

            //Configure EJB Deployments
            cdiPlugin.configureDeployments(stuff.getBeanContexts());

            //Resournce Injection Service
            final CdiResourceInjectionService injectionService = (CdiResourceInjectionService) webBeansContext.getService(ResourceInjectionService.class);
            // todo use startupObject allDeployments to find Comp in priority (otherwise we can keep N times comps and loose time at injection time
            injectionService.setAppContext(stuff.getAppContext(), stuff.getBeanContexts() != null ? stuff.getBeanContexts() : Collections.emptyList());

            //Deploy the beans
            CdiScanner cdiScanner = null;
            try {
                //Scanning process
                logger.debug("Scanning classpaths for beans artifacts.");

                if (CdiScanner.class.isInstance(scannerService)) {
                    cdiScanner = CdiScanner.class.cast(scannerService);
                    cdiScanner.setContext(webBeansContext);
                    cdiScanner.init(startupObject);
                } else {
                    cdiScanner = new CdiScanner();
                    cdiScanner.setContext(webBeansContext);
                    cdiScanner.init(startupObject);
                }

                //Scan
                this.scannerService.scan();

                // just to let us write custom CDI Extension using our internals easily
                CURRENT_APP_INFO.set(stuff.getAppInfo());

                addInternalBeans(); // before next event which can register custom beans (JAX-RS)
                SystemInstance.get().fireEvent(new WebBeansContextBeforeDeploy(webBeansContext));

                //Deploy bean from XML. Also configures deployments, interceptors, decorators.
                deployer.deploy(scannerService);
                contextsService.init(startupObject); // fire app event and also starts SingletonContext and ApplicationContext
            } catch (final Exception e1) {
                SystemInstance.get().getComponent(Assembler.class).logger.error("CDI Beans module deployment failed", e1);
                throw new OpenEJBRuntimeException(e1);
            } finally {
                CURRENT_APP_INFO.remove();
            }

            final Collection> ejbs = new ArrayList<>(stuff.getBeanContexts().size());
            for (final BeanContext bc : stuff.getBeanContexts()) {
                ejbs.add(bc.getManagedClass());

                final CdiEjbBean cdiEjbBean = bc.get(CdiEjbBean.class);
                if (cdiEjbBean == null) {
                    continue;
                }

                if (AbstractProducer.class.isInstance(cdiEjbBean)) {
                    AbstractProducer.class.cast(cdiEjbBean).defineInterceptorStack(cdiEjbBean, cdiEjbBean.getAnnotatedType(), cdiEjbBean.getWebBeansContext());
                }
                bc.mergeOWBAndOpenEJBInfo();
                bc.set(InterceptorResolutionService.BeanInterceptorInfo.class, InjectionTargetImpl.class.cast(cdiEjbBean.getInjectionTarget()).getInterceptorInfo());
                cdiEjbBean.initInternals();
            }

            //Start actual starting on sub-classes
            if (beanManager instanceof WebappBeanManager) {
                ((WebappBeanManager) beanManager).afterStart();
            }

            for (final Class clazz : cdiScanner.getStartupClasses()) {
                if (ejbs.contains(clazz)) {
                    continue;
                }
                starts(beanManager, clazz);
            }
        } finally {
            Thread.currentThread().setContextClassLoader(oldCl);

            // cleanup threadlocal used to enrich cdi context manually
            OptimizedLoaderService.ADDITIONAL_EXTENSIONS.remove();
        }

        logger.info("OpenWebBeans Container has started, it took {0} ms.", Long.toString(System.currentTimeMillis() - begin));
    }

    private void addInternalBeans() {
        beanManager.getInjectionResolver().clearCaches();

        if (!hasBean(beanManager, HttpServletRequest.class)) {
            beanManager.addInternalBean(new HttpServletRequestBean(webBeansContext));
        }
        if (!hasBean(beanManager, HttpSession.class)) {
            beanManager.addInternalBean(new InternalBean<>(webBeansContext, HttpSession.class, HttpSession.class));
        }
        if (!hasBean(beanManager, ServletContext.class)) {
            beanManager.addInternalBean(new InternalBean<>(webBeansContext, ServletContext.class, ServletContext.class));
        }

        beanManager.getInjectionResolver().clearCaches(); // hasBean() usage can have cached several things
    }

    private static boolean hasBean(final BeanManagerImpl beanManagerImpl, final Class type) {
        return !beanManagerImpl.getInjectionResolver().implResolveByType(false, type).isEmpty();
    }

    private void starts(final BeanManager beanManager, final Class clazz) {
        final Bean bean = beanManager.resolve(beanManager.getBeans(clazz));
        if (!beanManager.isNormalScope(bean.getScope())) {
            throw new IllegalStateException("Only normal scoped beans can use @Startup - likely @ApplicationScoped");
        }

        final CreationalContext creationalContext = beanManager.createCreationalContext(null);
        beanManager.getReference(bean, clazz, creationalContext).toString();
        // don't release now, will be done by the context - why we restrict it to normal scoped beans
    }

    @Override
    public void stopApplication(final Object endObject) {
        logger.debug("OpenWebBeans Container is stopping.");

        try {
            // Fire shut down
            if (WebappBeanManager.class.isInstance(beanManager)) {
                WebappBeanManager.class.cast(beanManager).beforeStop();
            }

            webBeansContext.getContextsService().endContext(RequestScoped.class, endObject);
            webBeansContext.getContextsService().endContext(ConversationScoped.class, endObject);
            webBeansContext.getContextsService().endContext(SessionScoped.class, endObject);
            webBeansContext.getContextsService().endContext(ApplicationScoped.class, endObject);
            webBeansContext.getContextsService().endContext(Singleton.class, endObject);

            // clean up the EL caches after each request
            ELContextStore elStore = ELContextStore.getInstance(false);
            if (elStore != null)
            {
                elStore.destroyELContextStore();
            }

            this.beanManager.fireEvent(new BeforeShutdownImpl(), true);

            // this will now even destroy the ExtensionBeans and other internal stuff
            this.contextsService.destroy(endObject);

            //Unbind BeanManager
            if (jndiService != null) {
                jndiService.unbind(WebBeansConstants.WEB_BEANS_MANAGER_JNDI_NAME);
            }

            //Free all plugin resources
            ((CdiPlugin) webBeansContext.getPluginLoader().getEjbPlugin()).clearProxies();
            webBeansContext.getPluginLoader().shutDown();

            //Clear extensions
            webBeansContext.getExtensionLoader().clear();

            //Delete Resolutions Cache
            beanManager.getInjectionResolver().clearCaches();

            //Delete AnnotateTypeCache
            webBeansContext.getAnnotatedElementFactory().clear();

            //After Stop
            //Clear the resource injection service
            final ResourceInjectionService injectionServices = webBeansContext.getService(ResourceInjectionService.class);
            if (injectionServices != null) {
                injectionServices.clear();
            }

            //Comment out for commit OWB-502
            //ContextFactory.cleanUpContextFactory();

            CdiAppContextsService.class.cast(contextsService).removeThreadLocals();

            WebBeansFinder.clearInstances(WebBeansUtil.getCurrentClassLoader());

            // Clear BeanManager
            this.beanManager.clear();

            // Clear singleton list
            WebBeansFinder.clearInstances(WebBeansUtil.getCurrentClassLoader());

        } catch (final Exception e) {
            logger.error("An error occured while stopping the container.", e);
        }

    }

    /**
     * @return the scannerService
     */
    protected ScannerService getScannerService() {
        return scannerService;
    }

    /**
     * @return the contextsService
     */
    public ContextsService getContextService() {
        return contextsService;
    }

    /**
     * @return the jndiService
     */
    protected JNDIService getJndiService() {
        return jndiService;
    }

    @Override
    public void initApplication(final Properties properties) {
        // no-op
    }

    public void startServletContext(final ServletContext servletContext) {
        initializeServletContext(servletContext, webBeansContext);
    }

    public static void initializeServletContext(final ServletContext servletContext, final WebBeansContext context) {
        if (context == null || !context.getBeanManagerImpl().isInUse()) {
            return;
        }

        final ELAdaptor elAdaptor = context.getService(ELAdaptor.class);
        final ELResolver resolver = elAdaptor.getOwbELResolver();
        //Application is configured as JSP
        if (context.getOpenWebBeansConfiguration().isJspApplication()) {
            logger.debug("Application is configured as JSP. Adding EL Resolver.");

            setJspELFactory(servletContext, resolver);
        }

        // Add BeanManager to the 'javax.enterprise.inject.spi.BeanManager' servlet context attribute
        servletContext.setAttribute(BeanManager.class.getName(), context.getBeanManagerImpl());
    }

    /**
     * On Tomcat we need to sometimes force a class load to get our hands on the JspFactory
     */
    private static void setJspELFactory(ServletContext startupObject, ELResolver resolver)
    {
        JspFactory factory = JspFactory.getDefaultFactory();
        if (factory == null)
        {
            try
            {
                try {
                    Class.forName("org.apache.jasper.servlet.JasperInitializer");
                } catch (final Throwable th) {
                    Class.forName("org.apache.jasper.compiler.JspRuntimeContext");
                }
                factory = JspFactory.getDefaultFactory();
            }
            catch (Exception e)
            {
                // ignore
            }

        }

        if (factory != null)
        {
            JspApplicationContext applicationCtx = factory.getJspApplicationContext(startupObject);
            applicationCtx.addELResolver(resolver);
        }
        else
        {
            logger.debug("Default JSPFactroy instance has not found. Skipping OWB JSP handling");
        }
    }


    /**
     * Returns servlet context otherwise throws exception.
     *
     * @param object object
     * @return servlet context
     */
    private Object getServletContext(Object object) {
        if (ServletContextEvent.class.isInstance(object)) {
            object = ServletContextEvent.class.cast(object).getServletContext();
            return object;
        }
        return object;
    }

    public static class InternalBean extends BuiltInOwbBean {
        private final String id;

        protected InternalBean(final WebBeansContext webBeansContext, final Class api, final Class type) {
            super(webBeansContext, WebBeansType.MANAGED, api,
                    new SimpleProducerFactory(
                            new ProviderBasedProducer<>(webBeansContext, type, new OpenEJBComponentProvider(webBeansContext, type), false)));
            this.id = "openejb#container#" + api.getName();
        }

        @Override
        public boolean isPassivationCapable() {
            return true;
        }

        @Override
        protected String providedId() {
            return id;
        }

        @Override
        public Class proxyableType() {
            return null;
        }
    }

    public static class HttpServletRequestBean extends InternalBean {
        private final Set types;

        protected HttpServletRequestBean(final WebBeansContext webBeansContext) {
            super(webBeansContext, HttpServletRequest.class, HttpServletRequest.class);
            this.types = new HashSet<>(); // here we need 2 types (+Object) otherwise decoratione etc fails
            this.types.add(HttpServletRequest.class);
            this.types.add(ServletRequest.class);
            this.types.add(Object.class);
        }

        @Override
        public Set getTypes() {
            return types;
        }
    }

    private static class OpenEJBComponentProvider implements Provider, Serializable {
        private Class type;
        private transient WebBeansContext webBeansContext;

        public OpenEJBComponentProvider(final WebBeansContext webBeansContext, final Class type) {
            this.webBeansContext = webBeansContext;
            this.type = type;
        }

        @Override
        public T get() {
            if (webBeansContext == null) {
                webBeansContext = WebBeansContext.currentInstance();
            }
            return (T) SystemInstance.get().getComponent(type);
        }

        Object readResolve() throws ObjectStreamException {
            return get();
        }
    }
}