blob: 4a948d8446b57eec5ac125471fdc595579465669 [file] [log] [blame]
/*
* 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.webbeans.web.context;
import org.apache.webbeans.annotation.DestroyedLiteral;
import org.apache.webbeans.annotation.InitializedLiteral;
import org.apache.webbeans.config.OWBLogConst;
import org.apache.webbeans.config.OpenWebBeansConfiguration;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.webbeans.context.AbstractContextsService;
import org.apache.webbeans.context.ApplicationContext;
import org.apache.webbeans.context.ConversationContext;
import org.apache.webbeans.context.DependentContext;
import org.apache.webbeans.context.RequestContext;
import org.apache.webbeans.context.SessionContext;
import org.apache.webbeans.context.SingletonContext;
import org.apache.webbeans.conversation.ConversationManager;
import org.apache.webbeans.el.ELContextStore;
import org.apache.webbeans.event.NotificationManager;
import org.apache.webbeans.intercept.SessionScopedBeanInterceptorHandler;
import org.apache.webbeans.logger.WebBeansLoggerFacade;
import org.apache.webbeans.intercept.RequestScopedBeanInterceptorHandler;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.ContextException;
import javax.enterprise.context.ConversationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.context.spi.Context;
import javax.inject.Singleton;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequestEvent;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.annotation.Annotation;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Web container {@link org.apache.webbeans.spi.ContextsService}
* implementation.
*/
public class WebContextsService extends AbstractContextsService
{
/**Logger instance*/
private static final Logger logger = WebBeansLoggerFacade.getLogger(WebContextsService.class);
private static final String OWB_SESSION_CONTEXT_ATTRIBUTE_NAME = "openWebBeansSessionContext";
/**
* TODO implement later: optional immediate destroy
*/
private final boolean destroySessionImmediately = false;
/**
* A single applicationContext
*/
protected ApplicationContext applicationContext;
protected SingletonContext singletonContext;
/**Current request context*/
protected ThreadLocal<ServletRequestContext> requestContexts = null;
/**Current session context*/
protected ThreadLocal<SessionContext> sessionContexts = null;
/**Current conversation context*/
protected ThreadLocal<ConversationContext> conversationContexts = null;
/**Current dependent context*/
protected DependentContext dependentContext;
/**Conversation context manager*/
protected final ConversationManager conversationManager;
protected Boolean eagerSessionInitialisation = null;
protected Pattern eagerSessionPattern = null;
protected Boolean fireRequestLifecycleEvents = null;
/**
* Creates a new instance.
*/
public WebContextsService(WebBeansContext webBeansContext)
{
super(webBeansContext);
conversationManager = webBeansContext.getConversationManager();
applicationContext = new ApplicationContext();
applicationContext.setActive(true);
requestContexts = new ThreadLocal<ServletRequestContext>();
sessionContexts = new ThreadLocal<SessionContext>();
conversationContexts = new ThreadLocal<ConversationContext>();
//Dependent context is always active
dependentContext = new DependentContext();
dependentContext.setActive(true);
configureEagerSessionInitialisation(webBeansContext);
}
protected void configureEagerSessionInitialisation(WebBeansContext webBeansContext)
{
String val = webBeansContext.getOpenWebBeansConfiguration().getProperty(OpenWebBeansConfiguration.EAGER_SESSION_INITIALISATION);
if (val == null || val.isEmpty() || "false".equalsIgnoreCase(val))
{
eagerSessionInitialisation = Boolean.FALSE;
logger.fine("EagerSessionInitialisation is configured to FALSE (Session will get created lazily on first use)");
}
else if ("true".equalsIgnoreCase(val))
{
eagerSessionInitialisation = Boolean.TRUE;
logger.fine("EagerSessionInitialisation is configured to TRUE (Session will get created at the beginning of a request)");
}
else
{
logger.fine("EagerSessionInitialisation used for all URIs with RegExp " + val);
eagerSessionPattern = Pattern.compile(val);
}
}
/**
* Removes the ThreadLocals from the ThreadMap to prevent memory leaks.
*/
public void removeThreadLocals()
{
requestContexts.remove();
sessionContexts.remove();
conversationContexts.remove();
RequestScopedBeanInterceptorHandler.removeThreadLocals();
}
/**
* {@inheritDoc}
*/
@Override
public void init(Object initializeObject)
{
//Start signelton context
startContext(Singleton.class, initializeObject);
//Start application context
startContext(ApplicationScoped.class, initializeObject);
}
/**
* {@inheritDoc}
*/
@Override
public void destroy(Object destroyObject)
{
RequestContext requestCtx = requestContexts.get();
if (requestCtx != null)
{
requestCtx.destroy();
requestContexts.set(null);
requestContexts.remove();
}
SessionContext sessionCtx = sessionContexts.get();
if (sessionCtx != null)
{
sessionCtx.destroy();
sessionContexts.set(null);
sessionContexts.remove();
}
ConversationContext conversationCtx = conversationContexts.get();
if (conversationCtx != null)
{
conversationCtx.destroy();
conversationContexts.set(null);
conversationContexts.remove();
}
if (singletonContext != null)
{
singletonContext.destroy();
singletonContext = null;
}
if (applicationContext != null)
{
applicationContext.destroy();
applicationContext.destroySystemBeans();
}
}
/**
* {@inheritDoc}
*/
@Override
public void endContext(Class<? extends Annotation> scopeType, Object endParameters)
{
if(scopeType.equals(RequestScoped.class))
{
destroyRequestContext(endParameters);
}
else if(scopeType.equals(SessionScoped.class))
{
destroySessionContext(endParameters);
}
else if(scopeType.equals(ApplicationScoped.class))
{
destroyApplicationContext(endParameters);
}
else if(supportsConversation && scopeType.equals(ConversationScoped.class))
{
destroyConversationContext();
}
else if(scopeType.equals(Dependent.class))
{
//Do nothing
return;
}
else if (scopeType.equals(Singleton.class))
{
destroySingletonContext(endParameters);
}
else
{
logger.warning("CDI-OpenWebBeans container does not support context scope "
+ scopeType.getSimpleName()
+ ". Scopes @Dependent, @RequestScoped, @ApplicationScoped and @Singleton are supported scope types");
}
}
@Override
public Context getCurrentContext(Class<? extends Annotation> scopeType, boolean createIfNotExists)
{
if(scopeType.equals(SessionScoped.class))
{
return getSessionContext(createIfNotExists);
}
return getCurrentContext(scopeType);
}
/**
* {@inheritDoc}
*/
@Override
public Context getCurrentContext(Class<? extends Annotation> scopeType)
{
if(scopeType.equals(RequestScoped.class))
{
return getRequestContext(true);
}
else if(scopeType.equals(SessionScoped.class))
{
return getSessionContext(true);
}
else if(scopeType.equals(ApplicationScoped.class))
{
return applicationContext;
}
else if(scopeType.equals(ConversationScoped.class))
{
return getConversationContext(true, false);
}
else if(scopeType.equals(Dependent.class))
{
return dependentContext;
}
else if (scopeType.equals(Singleton.class))
{
return singletonContext;
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void startContext(Class<? extends Annotation> scopeType, Object startParameter) throws ContextException
{
if (scopeType.equals(RequestScoped.class))
{
initRequestContext(startParameter);
}
else if (scopeType.equals(SessionScoped.class))
{
initSessionContext(startParameter);
}
else if (scopeType.equals(ApplicationScoped.class))
{
initApplicationContext(startParameter);
}
else if (supportsConversation && scopeType.equals(ConversationScoped.class))
{
initConversationContext(startParameter);
}
else if (scopeType.equals(Dependent.class))
{
//Do nothing
return;
}
else if (scopeType.equals(Singleton.class))
{
initSingletonContext(startParameter);
}
else
{
logger.warning("CDI-OpenWebBeans container does not support context scope "
+ scopeType.getSimpleName()
+ ". Scopes @Dependent, @RequestScoped, @ApplicationScoped and @Singleton are supported scope types");
}
}
/**
* Initialize requext context with the given request object.
* @param startupObject http servlet request event or system specific payload
*/
protected void initRequestContext(Object startupObject )
{
ServletRequestContext requestContext = new ServletRequestContext();
requestContext.setActive(true);
requestContexts.set(requestContext);// set thread local
Object payload = null;
if(startupObject instanceof ServletRequestEvent)
{
HttpServletRequest request = (HttpServletRequest) ((ServletRequestEvent) startupObject).getServletRequest();
requestContext.setServletRequest(request);
if (request != null)
{
payload = request;
if (shouldEagerlyInitializeSession(request))
{
request.getSession(true);
}
}
}
if (shouldFireRequestLifecycleEvents())
{
webBeansContext.getBeanManagerImpl().fireContextLifecyleEvent(
payload != null ? payload : new Object(), InitializedLiteral.INSTANCE_REQUEST_SCOPED);
}
}
protected boolean shouldEagerlyInitializeSession(HttpServletRequest request)
{
if (eagerSessionPattern != null)
{
String requestURI = request.getRequestURI();
Matcher matcher = eagerSessionPattern.matcher(requestURI);
return matcher.matches();
}
return eagerSessionInitialisation;
}
/**
* Destroys the request context and all of its components.
* @param endObject http servlet request object or other payload
*/
protected void destroyRequestContext(Object endObject)
{
//Get context
ServletRequestContext context = getRequestContext(false);
if (context == null)
{
return;
}
// cleanup open conversations first
if (supportsConversation)
{
destroyOutdatedConversations(conversationContexts.get());
}
if (context.getPropagatedSessionContext() != null)
{
SessionContext sessionContext = context.getPropagatedSessionContext();
sessionContext.destroy();
Object payload = null;
if (context.getServletRequest() != null)
{
payload = context.getHttpSession();
if (payload == null)
{
// in tomcat it will be null if invalidate was called
payload = context.getServletRequest().getSession(false);
}
}
webBeansContext.getBeanManagerImpl().fireContextLifecyleEvent(
payload != null ? payload : new Object(), DestroyedLiteral.INSTANCE_SESSION_SCOPED);
}
context.destroy();
// clean up the EL caches after each request
ELContextStore elStore = ELContextStore.getInstance(false);
if (elStore != null)
{
elStore.destroyELContextStore();
}
if (shouldFireRequestLifecycleEvents())
{
Object payload = null;
if (endObject != null && endObject instanceof ServletRequestEvent)
{
payload = ((ServletRequestEvent) endObject).getServletRequest();
}
webBeansContext.getBeanManagerImpl().fireContextLifecyleEvent(
payload != null ? payload : new Object(), DestroyedLiteral.INSTANCE_REQUEST_SCOPED);
}
// clean the proxy cache ThreadLocals
RequestScopedBeanInterceptorHandler.removeThreadLocals();
SessionScopedBeanInterceptorHandler.removeThreadLocals();
//Clear thread locals
requestContexts.set(null);
requestContexts.remove();
}
/**
* Creates the session context at the session start.
* @param startupObject HttpSession object
*/
protected void initSessionContext(Object startupObject)
{
SessionContext currentSessionContext;
HttpSession session = startupObject instanceof HttpSession ? (HttpSession) startupObject : null;
if (session != null)
{
// we need to get it latest here to make sure we work on the same instance
currentSessionContext = (SessionContext) session.getAttribute(OWB_SESSION_CONTEXT_ATTRIBUTE_NAME);
if (currentSessionContext == null)
{
// no current context, so lets create a new one
synchronized (session)
{
currentSessionContext = (SessionContext) session.getAttribute(OWB_SESSION_CONTEXT_ATTRIBUTE_NAME);
if (currentSessionContext == null)
{
currentSessionContext = new SessionContext();
currentSessionContext.setActive(true);
// init context before fire @Initialized(SessionScoped)
// so that SessionScoped beans are already available inside the observer
session.setAttribute(OWB_SESSION_CONTEXT_ATTRIBUTE_NAME, currentSessionContext);
sessionContexts.set(currentSessionContext);
webBeansContext.getBeanManagerImpl().fireContextLifecyleEvent(
session, InitializedLiteral.INSTANCE_SESSION_SCOPED);
}
}
}
else
{
// we do that in any case.
// This is needed to trigger delta-replication on most servers
session.setAttribute(OWB_SESSION_CONTEXT_ATTRIBUTE_NAME, currentSessionContext);
currentSessionContext.setActive(true);
//Set thread local
sessionContexts.set(currentSessionContext);
}
}
}
/**
* Destroys the session context and all of its components at the end of the
* session.
* @param endObject http session object. Can be {@code null} or different object for non-http SessionContexts. Such a context only lives for one thread.
*/
protected void destroySessionContext(Object endObject)
{
// Get current session context from ThreadLocal
SessionContext context = sessionContexts.get();
HttpSession session = null;
// whether the session is destroyed because it is expired
boolean sessionIsExpiring = false;
if (endObject instanceof HttpSession)
{
session = (HttpSession) endObject;
if (context == null && session.getAttribute(OWB_SESSION_CONTEXT_ATTRIBUTE_NAME) != null)
{
if (!destroySessionImmediately)
{
sessionIsExpiring = sessionIsExpiring(session);
}
// init in this case only attaches the existing session to the ThreadLocal
initSessionContext(session);
context = sessionContexts.get();
}
}
// Destroy context
if (context != null && context.isActive())
{
// we need to mark the conversation to get destroyed at the end of the request
ServletRequestContext requestContext = getRequestContext(true);
if (destroySessionImmediately
|| requestContext == null || requestContext.getServletRequest() == null
|| requestContext.getServletRequest().getSession(false) == null
|| sessionIsExpiring)
{
context.destroy();
webBeansContext.getBeanManagerImpl().fireContextLifecyleEvent(
session != null ? session : new Object(), DestroyedLiteral.INSTANCE_SESSION_SCOPED);
// Clear thread locals
sessionContexts.set(null);
sessionContexts.remove();
}
else
{
requestContext.setPropagatedSessionContext(context);
// this is to be spec compliant but depending the servlet container
// it can be dangerous if sessions are pooled (ie you can fire a session used by another request)
requestContext.setHttpSession(session);
}
}
SessionScopedBeanInterceptorHandler.removeThreadLocals();
}
/**
* @return {@code true} if the sessino is currently expiring or has already expired
*/
protected boolean sessionIsExpiring(HttpSession session)
{
int maxInactiveInterval = session.getMaxInactiveInterval();
if (maxInactiveInterval > 0)
{
long inactiveSince = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - session.getLastAccessedTime());
if (inactiveSince >= maxInactiveInterval)
{
return true;
}
}
return false;
}
/**
* Creates the application context at the application startup
* @param startupObject servlet context object or other startup
*
*/
protected void initApplicationContext(Object startupObject)
{
if (applicationContext != null && !applicationContext.isDestroyed())
{
applicationContext.setActive(true);
return;
}
ApplicationContext newApplicationContext = new ApplicationContext();
newApplicationContext.setActive(true);
if (applicationContext == null)
{
applicationContext = newApplicationContext;
}
}
/**
* Destroys the application context and all of its components at the end of
* the application.
* @param endObject servlet context object or other payload
*/
protected void destroyApplicationContext(Object endObject)
{
//Destroy context
if(applicationContext != null && !applicationContext.isDestroyed())
{
applicationContext.destroy();
// this is needed to get rid of ApplicationScoped beans which are cached inside the proxies...
webBeansContext.getBeanManagerImpl().clearCacheProxies();
Object payload = endObject instanceof ServletContext ? endObject : new Object();
webBeansContext.getBeanManagerImpl().fireContextLifecyleEvent(
payload, DestroyedLiteral.INSTANCE_APPLICATION_SCOPED);
}
}
/**
* Initialize singleton context.
* @param startupObject servlet context
*/
protected void initSingletonContext(Object startupObject)
{
if (singletonContext != null)
{
return;
}
synchronized (this)
{
if (singletonContext == null)
{
singletonContext = new SingletonContext();
singletonContext.setActive(true);
Object payLoad = startupObject instanceof ServletContext
? (ServletContext) startupObject : new Object();
webBeansContext.getBeanManagerImpl().fireContextLifecyleEvent(
payLoad, InitializedLiteral.INSTANCE_SINGLETON_SCOPED);
}
}
}
/**
* Destroy singleton context.
* @param endObject servlet context or other payload
*/
protected void destroySingletonContext(Object endObject)
{
if (singletonContext != null)
{
singletonContext.destroy();
singletonContext = null;
Object payload = endObject != null ? endObject : new Object();
webBeansContext.getBeanManagerImpl().fireContextLifecyleEvent(
payload, DestroyedLiteral.INSTANCE_SINGLETON_SCOPED);
}
}
/**
* Initialize conversation context.
* @param startObject either a ServletRequest or a ConversationContext
*/
protected void initConversationContext(Object startObject)
{
if (conversationContexts.get() != null)
{
return;
}
if (startObject instanceof ConversationContext)
{
//X TODO check if this branch is still needed
ConversationContext context = (ConversationContext) startObject;
context.setActive(true);
conversationContexts.set(context);
}
}
/**
* Destroy conversation context.
*/
protected void destroyConversationContext()
{
if (conversationContexts.get() == null)
{
return;
}
ConversationContext context = getConversationContext(false, true);
if (context != null)
{
context.destroy();
webBeansContext.getBeanManagerImpl().fireContextLifecyleEvent(
new Object(), DestroyedLiteral.INSTANCE_SINGLETON_SCOPED);
}
conversationContexts.set(null);
conversationContexts.remove();
}
/**
* Get current request ctx.
* @return request context
*/
public ServletRequestContext getRequestContext(boolean create)
{
ServletRequestContext requestContext = requestContexts.get();
if (requestContext == null && create)
{
initRequestContext(null);
}
return requestContext;
}
/**
* Get current session ctx or lazily create one.
* @return session context
* @param forceCreate if {@code true} we will force creating a session if not yet exists.
* if {@code false} we will only create a SessionContext if a HttpSession already exists
*/
public SessionContext getSessionContext(boolean forceCreate)
{
SessionContext context = sessionContexts.get();
if (null == context)
{
lazyStartSessionContext(forceCreate);
context = sessionContexts.get();
}
return context;
}
/**
* Get current conversation ctx.
* @return conversation context
*/
public ConversationContext getConversationContext(boolean create, boolean ignoreProblems)
{
ConversationContext conversationContext = conversationContexts.get();
if (conversationContext == null)
{
SessionContext sessionContext = getSessionContext(create);
if (sessionContext != null)
{
conversationContext = conversationManager.getConversationContext(sessionContext);
conversationContexts.set(conversationContext);
if (conversationContext.getConversation().isTransient())
{
webBeansContext.getBeanManagerImpl().fireContextLifecyleEvent(
conversationManager.getLifecycleEventPayload(conversationContext),
InitializedLiteral.INSTANCE_CONVERSATION_SCOPED);
}
if (!ignoreProblems && conversationContext.getConversation().getProblemDuringCreation() != null)
{
throw conversationContext.getConversation().getProblemDuringCreation();
}
}
}
return conversationContext;
}
protected boolean shouldFireRequestLifecycleEvents()
{
if (fireRequestLifecycleEvents == null)
{
NotificationManager notificationManager = webBeansContext.getBeanManagerImpl().getNotificationManager();
fireRequestLifecycleEvents
= notificationManager.hasContextLifecycleObserver(InitializedLiteral.INSTANCE_REQUEST_SCOPED) ||
notificationManager.hasContextLifecycleObserver(DestroyedLiteral.INSTANCE_REQUEST_SCOPED);
}
return fireRequestLifecycleEvents;
}
/**
* Try to lazily start the sessionContext.
* First we try to find a real HttpSession and create the SessionContext in there.
* If this is not possible and the {@param allowSynthecticSession} is {@code true} then
* we will
* @param createSession if {@code false} then we will only create a SessionContext if a HttpSession already exists
*/
private void lazyStartSessionContext(boolean createSession)
{
if (logger.isLoggable(Level.FINE))
{
logger.log(Level.FINE, ">lazyStartSessionContext");
}
ServletRequestContext requestContext = getRequestContext(true);
if (requestContext == null)
{
logger.log(Level.WARNING, "Could NOT lazily initialize session context because NO active request context");
return;
}
HttpServletRequest servletRequest = requestContext.getServletRequest();
// this could be null if there is no active request context
if (servletRequest != null)
{
try
{
HttpSession currentSession = servletRequest.getSession(createSession);
if (currentSession != null)
{
initSessionContext(currentSession);
if (logger.isLoggable(Level.FINE))
{
logger.log(Level.FINE, "Lazy SESSION context initialization SUCCESS");
}
return;
}
}
catch (Exception e)
{
logger.log(Level.SEVERE, WebBeansLoggerFacade.constructMessage(OWBLogConst.ERROR_0013, e));
}
}
}
}