/*
* 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<AppInfo> CURRENT_APP_INFO = new ThreadLocal<AppInfo>();
//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.<BeanContext>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<Class<?>> 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<Object> 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<T> extends BuiltInOwbBean<T> {
private final String id;
protected InternalBean(final WebBeansContext webBeansContext, final Class<T> api, final Class<?> type) {
super(webBeansContext, WebBeansType.MANAGED, api,
new SimpleProducerFactory<T>(
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<HttpServletRequest> {
private final Set<Type> 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<Type> getTypes() {
return types;
}
}
private static class OpenEJBComponentProvider<T> implements Provider<T>, 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();
}
}
}