/*
* 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.loader.SystemInstance;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import org.apache.webbeans.config.OWBLogConst;
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.ConversationImpl;
import org.apache.webbeans.conversation.ConversationManager;
import org.apache.webbeans.el.ELContextStore;
import org.apache.webbeans.spi.ContextsService;
import org.apache.webbeans.spi.ConversationService;
import org.apache.webbeans.web.context.ServletRequestContext;
import org.apache.webbeans.web.intercept.RequestScopedBeanInterceptorHandler;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.ContextException;
import javax.enterprise.context.Conversation;
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.ServletRequestEvent;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
public class CdiAppContextsService extends AbstractContextsService implements ContextsService {
private static final Logger logger = Logger.getInstance(LogCategory.OPENEJB.createChild("cdi"), CdiAppContextsService.class);
private final ThreadLocal<RequestContext> requestContext = new ThreadLocal<RequestContext>();
private final ThreadLocal<SessionContext> sessionContext = new ThreadLocal<SessionContext>();
private final UpdatableSessionContextManager sessionCtxManager = new UpdatableSessionContextManager();
/**
* Conversation context manager
*/
private final ThreadLocal<ConversationContext> conversationContext;
private final DependentContext dependentContext = new DependentContext();
private final ApplicationContext applicationContext = new ApplicationContext();
private final SingletonContext singletonContext = new SingletonContext();
private final WebBeansContext webBeansContext;
private static final ThreadLocal<Collection<Runnable>> endRequestRunnables = new ThreadLocal<Collection<Runnable>>() {
@Override
protected Collection<Runnable> initialValue() {
return new ArrayList<Runnable>();
}
};
public CdiAppContextsService() {
this(WebBeansContext.currentInstance(), WebBeansContext.currentInstance().getOpenWebBeansConfiguration().supportsConversation());
}
public CdiAppContextsService(final WebBeansContext wbc, final boolean supportsConversation) {
if (wbc != null) {
webBeansContext = wbc;
} else {
webBeansContext = WebBeansContext.currentInstance();
}
dependentContext.setActive(true);
if (supportsConversation) {
conversationContext = new ThreadLocal<ConversationContext>();
} else {
conversationContext = null;
}
applicationContext.setActive(true);
singletonContext.setActive(true);
}
private void endRequest() {
for (final Runnable r : endRequestRunnables.get()) {
try {
r.run();
} catch (final Exception e) {
logger.error(e.getMessage(), e);
}
}
endRequestRunnables.remove();
}
public static void pushRequestReleasable(final Runnable runnable) {
endRequestRunnables.get().add(runnable);
}
@Override
public void init(final Object initializeObject) {
//Start application context
startContext(ApplicationScoped.class, initializeObject);
//Start signelton context
startContext(Singleton.class, initializeObject);
}
public void destroy(final Object destroyObject) {
//Destroy application context
endContext(ApplicationScoped.class, destroyObject);
//Destroy singleton context
endContext(Singleton.class, destroyObject);
removeThreadLocals();
}
public void removeThreadLocals() {
//Remove thread locals
//for preventing memory leaks
requestContext.set(null);
requestContext.remove();
sessionContext.set(null);
sessionContext.remove();
if (null != conversationContext) {
conversationContext.set(null);
conversationContext.remove();
}
}
@Override
public void endContext(final Class<? extends Annotation> scopeType, final Object endParameters) {
if (supportsContext(scopeType)) {
if (scopeType.equals(RequestScoped.class)) {
destroyRequestContext();
} else if (scopeType.equals(SessionScoped.class)) {
destroySessionContext((HttpSession) endParameters);
} else if (scopeType.equals(ApplicationScoped.class)) {
destroyApplicationContext();
} else if (scopeType.equals(Dependent.class)) { //NOPMD
// Do nothing
} else if (scopeType.equals(Singleton.class)) {
destroySingletonContext();
} else if (supportsConversation() && scopeType.equals(ConversationScoped.class)) {
destroyConversationContext();
} else {
if (logger.isWarningEnabled()) {
logger.warning("CDI-OpenWebBeans container in OpenEJB does not support context scope "
+ scopeType.getSimpleName()
+ ". Scopes @Dependent, @RequestScoped, @ApplicationScoped and @Singleton are supported scope types");
}
}
}
}
@Override
public Context getCurrentContext(final Class<? extends Annotation> scopeType) {
if (scopeType.equals(RequestScoped.class)) {
return getRequestContext();
} else if (scopeType.equals(SessionScoped.class)) {
return getSessionContext();
} else if (scopeType.equals(ApplicationScoped.class)) {
return getApplicationContext();
} else if (supportsConversation() && scopeType.equals(ConversationScoped.class)) {
return getConversationContext();
} else if (scopeType.equals(Dependent.class)) {
return dependentContext;
} else if (scopeType.equals(Singleton.class)) {
return getSingletonContext();
}
return null;
}
@Override
public void startContext(final Class<? extends Annotation> scopeType, final Object startParameter) throws ContextException {
if (supportsContext(scopeType)) {
if (scopeType.equals(RequestScoped.class)) {
initRequestContext((ServletRequestEvent) startParameter);
} else if (scopeType.equals(SessionScoped.class)) {
initSessionContext((HttpSession) startParameter);
} else if (scopeType.equals(ApplicationScoped.class)) {
initApplicationContext();
} else if (scopeType.equals(Dependent.class)) {
initSingletonContext();
} else if (scopeType.equals(Singleton.class)) { //NOPMD
// Do nothing
} else if (supportsConversation() && scopeType.equals(ConversationScoped.class)) {
initConversationContext((ConversationContext) startParameter);
} else {
if (logger.isWarningEnabled()) {
logger.warning("CDI-OpenWebBeans container in OpenEJB does not support context scope "
+ scopeType.getSimpleName()
+ ". Scopes @Dependent, @RequestScoped, @ApplicationScoped and @Singleton are supported scope types");
}
}
}
}
private void initSingletonContext() {
singletonContext.setActive(true);
}
private void initApplicationContext() { // in case contexts are stop/start
applicationContext.setActive(true);
}
@Override
public boolean supportsContext(final Class<? extends Annotation> scopeType) {
return scopeType.equals(RequestScoped.class)
|| scopeType.equals(SessionScoped.class)
|| scopeType.equals(ApplicationScoped.class)
|| scopeType.equals(Dependent.class)
|| scopeType.equals(Singleton.class)
|| scopeType.equals(ConversationScoped.class) && supportsConversation();
}
private void initRequestContext(final ServletRequestEvent event) {
final RequestContext rq = new ServletRequestContext();
rq.setActive(true);
requestContext.set(rq);// set thread local
if (event != null) {
final HttpServletRequest request = (HttpServletRequest) event.getServletRequest();
((ServletRequestContext) rq).setServletRequest(request);
if (request != null) {
//Re-initialize thread local for session
final HttpSession session = request.getSession(false);
if (session != null) {
initSessionContext(session);
}
}
}
}
private void destroyRequestContext() {
// execute request tasks
endRequest();
if (supportsConversation()) { // OWB-595
cleanupConversation();
}
//Get context
final RequestContext context = getRequestContext();
//Destroy context
if (context != null) {
context.destroy();
}
// clean up the EL caches after each request
final ELContextStore elStore = ELContextStore.getInstance(false);
if (elStore != null) {
elStore.destroyELContextStore();
}
//Clear thread locals - only for request to let user do with deltaspike start(session, request)restart(request)...stop()
requestContext.remove();
RequestScopedBeanInterceptorHandler.removeThreadLocals();
}
private void cleanupConversation() {
if (webBeansContext.getService(ConversationService.class) == null) {
return;
}
final ConversationContext conversationContext = getConversationContext();
if (conversationContext == null) {
return;
}
final ConversationManager conversationManager = webBeansContext.getConversationManager();
final Conversation conversation = conversationManager.getConversationBeanReference();
if (conversation == null) {
return;
}
if (conversation.isTransient()) {
webBeansContext.getContextsService().endContext(ConversationScoped.class, null);
} else {
final ConversationImpl conversationImpl = (ConversationImpl) conversation;
conversationImpl.updateTimeOut();
conversationImpl.setInUsed(false);
}
}
/**
* Creates the session context at the session start.
*
* @param session http session object
*/
private void initSessionContext(final HttpSession session) {
if (session == null) {
// no session -> no SessionContext
return;
}
final String sessionId = session.getId();
//Current context
SessionContext currentSessionContext = sessionCtxManager.getSessionContextWithSessionId(sessionId);
//No current context
if (currentSessionContext == null) {
currentSessionContext = newSessionContext(session);
sessionCtxManager.addNewSessionContext(sessionId, currentSessionContext);
}
//Activate
currentSessionContext.setActive(true);
//Set thread local
sessionContext.set(currentSessionContext);
}
private SessionContext newSessionContext(final HttpSession session) {
final String classname = SystemInstance.get().getComponent(ThreadSingletonService.class).sessionContextClass();
if (classname != null) {
try {
final Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(classname);
try {
final Constructor<?> constr = clazz.getConstructor(HttpSession.class);
return (SessionContext) constr.newInstance(session);
} catch (final Exception e) {
return (SessionContext) clazz.newInstance();
}
} catch (final Exception e) {
logger.error("Can't instantiate " + classname + ", using default session context", e);
}
}
return new SessionContext();
}
/**
* Destroys the session context and all of its components at the end of the
* session.
*
* @param session http session object
*/
private void destroySessionContext(final HttpSession session) {
if (session != null) {
//Get current session context
final SessionContext context = sessionContext.get();
//Destroy context
if (context != null) {
context.destroy();
}
//Clear thread locals
sessionContext.set(null);
sessionContext.remove();
//Remove session from manager
sessionCtxManager.removeSessionContextWithSessionId(session.getId());
}
}
//we don't have initApplicationContext
private void destroyApplicationContext() {
applicationContext.destroy();
}
private void destroySingletonContext() {
singletonContext.destroy();
}
/**
* Initialize conversation context.
*
* @param context context
*/
private void initConversationContext(final ConversationContext context) {
if (webBeansContext.getService(ConversationService.class) == null) {
return;
}
if (context == null) {
if (conversationContext.get() == null) {
final ConversationContext newContext = new ConversationContext();
newContext.setActive(true);
conversationContext.set(newContext);
} else {
conversationContext.get().setActive(true);
}
} else {
context.setActive(true);
conversationContext.set(context);
}
}
/**
* Destroy conversation context.
*/
private void destroyConversationContext() {
if (webBeansContext.getService(ConversationService.class) == null) {
return;
}
final ConversationContext context = getConversationContext();
if (context != null) {
context.destroy();
}
if (null != conversationContext) {
conversationContext.set(null);
conversationContext.remove();
}
}
private RequestContext getRequestContext() {
return requestContext.get();
}
private Context getSessionContext() {
SessionContext context = sessionContext.get();
if (context == null || !context.isActive()) {
lazyStartSessionContext();
context = sessionContext.get();
}
return context;
}
/**
* Gets application context.
*
* @return application context
*/
private ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* Gets singleton context.
*
* @return singleton context
*/
private SingletonContext getSingletonContext() {
return singletonContext;
}
/**
* Get current conversation ctx.
*
* @return conversation context
*/
private ConversationContext getConversationContext() {
return conversationContext.get();
}
private Context lazyStartSessionContext() {
if (logger.isDebugEnabled()) {
logger.debug(">lazyStartSessionContext");
}
final Context webContext = null;
final Context context = getCurrentContext(RequestScoped.class);
if (context instanceof ServletRequestContext) {
final ServletRequestContext requestContext = (ServletRequestContext) context;
final HttpServletRequest servletRequest = requestContext.getServletRequest();
if (null != servletRequest) { // this could be null if there is no active request context
try {
final HttpSession currentSession = servletRequest.getSession();
initSessionContext(currentSession);
/*
final FailOverService failoverService = webBeansContext.getService(FailOverService.class);
if (failoverService != null && failoverService.isSupportFailOver()) {
failoverService.sessionIsInUse(currentSession);
}
*/
if (logger.isDebugEnabled()) {
logger.debug("Lazy SESSION context initialization SUCCESS");
}
} catch (final Exception e) {
logger.error(OWBLogConst.ERROR_0013, e);
}
} else {
logger.warning("Could NOT lazily initialize session context because NO active request context");
}
} else {
logger.warning("Could NOT lazily initialize session context because of " + context + " RequestContext");
}
if (logger.isDebugEnabled()) {
logger.debug("<lazyStartSessionContext " + webContext);
}
return webContext;
}
private boolean supportsConversation() {
return conversationContext != null;
}
public void updateSessionIdMapping(final String oldId, final String newId) {
sessionCtxManager.updateSessionIdMapping(oldId, newId);
}
public State saveState() {
return new State(requestContext.get(), sessionContext.get(), conversationContext.get());
}
public State restoreState(final State state) {
final State old = saveState();
requestContext.set(state.request);
sessionContext.set(state.session);
conversationContext.set(state.conversation);
return old;
}
public static class State {
private final RequestContext request;
private final SessionContext session;
private final ConversationContext conversation;
public State(final RequestContext request, final SessionContext session, final ConversationContext conversation) {
this.request = request;
this.session = session;
this.conversation = conversation;
}
}
}