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

org.jboss.weld.servlet.HttpContextLifecycle Maven / Gradle / Ivy

There is a newer version: 3.0.0.Alpha1
Show newest version
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2013, Red Hat, Inc., and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * Licensed 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.jboss.weld.servlet;

import java.lang.annotation.Annotation;
import java.util.Collections;

import javax.enterprise.inject.spi.EventMetadata;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.jboss.weld.Container;
import org.jboss.weld.bootstrap.BeanDeploymentModule;
import org.jboss.weld.bootstrap.BeanDeploymentModules;
import org.jboss.weld.bootstrap.api.Service;
import org.jboss.weld.context.BoundContext;
import org.jboss.weld.context.ManagedContext;
import org.jboss.weld.context.cache.RequestScopedCache;
import org.jboss.weld.context.http.HttpRequestContext;
import org.jboss.weld.context.http.HttpRequestContextImpl;
import org.jboss.weld.context.http.HttpSessionContext;
import org.jboss.weld.context.http.HttpSessionDestructionContext;
import org.jboss.weld.event.EventMetadataImpl;
import org.jboss.weld.event.FastEvent;
import org.jboss.weld.literal.DestroyedLiteral;
import org.jboss.weld.literal.InitializedLiteral;
import org.jboss.weld.logging.ServletLogger;
import org.jboss.weld.manager.BeanManagerImpl;
import org.jboss.weld.servlet.spi.HttpContextActivationFilter;
import org.jboss.weld.util.reflection.Reflections;

/**
 * Takes care of setting up and tearing down CDI contexts around an HTTP request and dispatching context lifecycle events.
 *
 * @author Jozef Hartinger
 * @author Marko Luksa
 *
 */
public class HttpContextLifecycle implements Service {

    public static final String ASYNC_STARTED_ATTR_NAME = "org.jboss.weld.context.asyncStarted";

    private static final String HTTP_SESSION = "org.jboss.weld." + HttpSession.class.getName();

    private static final String INCLUDE_HEADER = "javax.servlet.include.request_uri";
    private static final String FORWARD_HEADER = "javax.servlet.forward.request_uri";
    private static final String REQUEST_DESTROYED = HttpContextLifecycle.class.getName() + ".request.destroyed";

    private static final String GUARD_PARAMETER_NAME = "org.jboss.weld.context.ignore.guard.marker";
    private static final Object GUARD_PARAMETER_VALUE = new Object();

    private HttpSessionDestructionContext sessionDestructionContextCache;
    private HttpSessionContext sessionContextCache;
    private HttpRequestContext requestContextCache;

    private volatile Boolean conversationActivationEnabled;
    private final boolean ignoreForwards;
    private final boolean ignoreIncludes;

    private final BeanManagerImpl beanManager;
    private final ConversationContextActivator conversationContextActivator;
    private final HttpContextActivationFilter contextActivationFilter;

    private final FastEvent requestInitializedEvent;
    private final FastEvent requestDestroyedEvent;
    private final FastEvent sessionInitializedEvent;
    private final FastEvent sessionDestroyedEvent;

    private final ServletApiAbstraction servletApi;

    private final ServletContextService servletContextService;

    private final Container container;
    private final BeanDeploymentModule module;

    private static final ThreadLocal nestedInvocationGuard = new ThreadLocal();
    private final boolean nestedInvocationGuardEnabled;

    private static class Counter {
        private int value = 1;
    }

    public HttpContextLifecycle(BeanManagerImpl beanManager, HttpContextActivationFilter contextActivationFilter, boolean ignoreForwards,
            boolean ignoreIncludes, boolean lazyConversationContext, boolean nestedInvocationGuardEnabled) {
        this.beanManager = beanManager;
        this.conversationContextActivator = new ConversationContextActivator(beanManager, lazyConversationContext);
        this.conversationActivationEnabled = null;
        this.ignoreForwards = ignoreForwards;
        this.ignoreIncludes = ignoreIncludes;
        this.contextActivationFilter = contextActivationFilter;
        this.requestInitializedEvent = FastEvent.of(HttpServletRequest.class, beanManager, InitializedLiteral.REQUEST);
        this.requestDestroyedEvent = FastEvent.of(HttpServletRequest.class, beanManager, DestroyedLiteral.REQUEST);
        this.sessionInitializedEvent = FastEvent.of(HttpSession.class, beanManager, InitializedLiteral.SESSION);
        this.sessionDestroyedEvent = FastEvent.of(HttpSession.class, beanManager, DestroyedLiteral.SESSION);
        this.servletApi = beanManager.getServices().get(ServletApiAbstraction.class);
        this.servletContextService = beanManager.getServices().get(ServletContextService.class);
        this.nestedInvocationGuardEnabled = nestedInvocationGuardEnabled;
        this.container = Container.instance(beanManager);
        BeanDeploymentModules beanDeploymentModules = beanManager.getServices().get(BeanDeploymentModules.class);
        this.module = beanDeploymentModules != null ? beanDeploymentModules.getModule(beanManager) : null;
    }

    private HttpSessionDestructionContext getSessionDestructionContext() {
        if (sessionDestructionContextCache == null) {
            this.sessionDestructionContextCache = beanManager.instance().select(HttpSessionDestructionContext.class).get();
        }
        return sessionDestructionContextCache;
    }

    private HttpSessionContext getSessionContext() {
        if (sessionContextCache == null) {
            this.sessionContextCache = beanManager.instance().select(HttpSessionContext.class).get();
        }
        return sessionContextCache;
    }

    public HttpRequestContext getRequestContext() {
        if (requestContextCache == null) {
            this.requestContextCache = beanManager.instance().select(HttpRequestContext.class).get();
        }
        return requestContextCache;
    }

    public void contextInitialized(ServletContext ctx) {
        servletContextService.contextInitialized(ctx);
        fireEventForApplicationScope(ctx, InitializedLiteral.APPLICATION);
    }

    public void contextDestroyed(ServletContext ctx) {
        fireEventForApplicationScope(ctx, DestroyedLiteral.APPLICATION);
    }

    private void fireEventForApplicationScope(ServletContext ctx, Annotation qualifier) {
        if (module != null) {
            // Deliver events sequentially
            synchronized (container) {
                if (module.isWebModule()) {
                    module.fireEvent(ServletContext.class, ctx, qualifier);
                } else {
                    // fallback for backward compatibility
                    final EventMetadata metadata = new EventMetadataImpl(ServletContext.class, null, Collections.singleton(qualifier));
                    beanManager.getAccessibleLenientObserverNotifier().fireEvent(ServletContext.class, ctx, metadata, qualifier);
                }
            }
        }
    }

    public void sessionCreated(HttpSession session) {
        SessionHolder.sessionCreated(session);
        conversationContextActivator.sessionCreated(session);
        sessionInitializedEvent.fire(session);
    }

    public void sessionDestroyed(HttpSession session) {
        // Mark the session context and conversation contexts to destroy
        // instances when appropriate
        deactivateSessionDestructionContext(session);
        boolean destroyed = getSessionContext().destroy(session);
        SessionHolder.clear();
        RequestScopedCache.endRequest();
        if (destroyed) {
            // we are outside of a request (the session timed out) and therefore the session was destroyed immediately
            // we can fire the @Destroyed(SessionScoped.class) event immediately
            sessionDestroyedEvent.fire(session);
        } else {
            // the old session won't be available at the time we destroy this request
            // let's store its reference until then
            if (getRequestContext() instanceof HttpRequestContextImpl) {
                HttpServletRequest request = Reflections. cast(getRequestContext()).getHttpServletRequest();
                request.setAttribute(HTTP_SESSION, session);
            }
        }
    }

    private void deactivateSessionDestructionContext(HttpSession session) {
        HttpSessionDestructionContext context = getSessionDestructionContext();
        if (context.isActive()) {
            context.deactivate();
            context.dissociate(session);
        }
    }

    public void requestInitialized(HttpServletRequest request, ServletContext ctx) {
        if (nestedInvocationGuardEnabled) {
            Counter counter = nestedInvocationGuard.get();
            Object marker = request.getAttribute(GUARD_PARAMETER_NAME);
            if (counter != null && marker != null) {
                // this is a nested invocation, increment the counter and ignore this invocation
                counter.value++;
                return;
            } else {
                if (counter != null && marker == null) {
                    /*
                     * This request has not been processed yet but the guard is set already. That indicates, that the guard leaked from a previous request
                     * processing - most likely the Servlet container did not invoke listener methods symmetrically. Log a warning and recover by
                     * re-initializing the guard
                     */
                    ServletLogger.LOG.guardLeak(counter.value);
                }
                // this is the initial (outer) invocation
                nestedInvocationGuard.set(new Counter());
                request.setAttribute(GUARD_PARAMETER_NAME, GUARD_PARAMETER_VALUE);
            }
        }
        if (ignoreForwards && isForwardedRequest(request)) {
            return;
        }
        if (ignoreIncludes && isIncludedRequest(request)) {
            return;
        }
        if (!contextActivationFilter.accepts(request)) {
            return;
        }

        ServletLogger.LOG.requestInitialized(request);

        SessionHolder.requestInitialized(request);

        getRequestContext().associate(request);
        getSessionContext().associate(request);
        if (conversationActivationEnabled) {
            conversationContextActivator.associateConversationContext(request);
        }

        getRequestContext().activate();
        getSessionContext().activate();

        try {
            if (conversationActivationEnabled) {
                conversationContextActivator.activateConversationContext(request);
            }
            requestInitializedEvent.fire(request);
        } catch (RuntimeException e) {
            try {
                requestDestroyed(request);
            } catch (Exception ignored) {
                // ignored in order to let the original exception be thrown
            }
            /*
             * If the servlet container happens to call the destroyed callback again, ignore it.
             */
            request.setAttribute(REQUEST_DESTROYED, Boolean.TRUE);
            throw e;
        }
    }

    public void requestDestroyed(HttpServletRequest request) {
        if (isRequestDestroyed(request)) {
            return;
        }
        if (nestedInvocationGuardEnabled) {
            Counter counter = nestedInvocationGuard.get();
            if (counter != null) {
                counter.value--;
                if (counter.value > 0) {
                    return; // this is a nested invocation, ignore it
                } else {
                    nestedInvocationGuard.remove(); // this is the outer invocation
                    request.removeAttribute(GUARD_PARAMETER_NAME);
                }
            } else {
                ServletLogger.LOG.guardNotSet();
                return;
            }
        }
        if (ignoreForwards && isForwardedRequest(request)) {
            return;
        }
        if (ignoreIncludes && isIncludedRequest(request)) {
            return;
        }
        if (!contextActivationFilter.accepts(request)) {
            return;
        }

        ServletLogger.LOG.requestDestroyed(request);

        try {
            conversationContextActivator.deactivateConversationContext(request);
            /*
             * If this request has been switched to async then do not invalidate the context now as it will be invalidated at the end of the async operation.
             */
            if (servletApi.isAsyncSupported() && servletApi.isAsyncStarted(request)) {
                // Note that we can't use isAsyncStarted() because it may return false after dispatch
                request.setAttribute(ASYNC_STARTED_ATTR_NAME, true);
            } else {
                getRequestContext().invalidate();
            }

            safelyDeactivate(getRequestContext(), request);
            // fire @Destroyed(RequestScoped.class)
            requestDestroyedEvent.fire(request);

            safelyDeactivate(getSessionContext(), request);
            // fire @Destroyed(SessionScoped.class)
            Object destroyedHttpSession = request.getAttribute(HTTP_SESSION);
            if (destroyedHttpSession != null) {
                sessionDestroyedEvent.fire((HttpSession) destroyedHttpSession);
            }
        } finally {
            safelyDissociate(getRequestContext(), request);
            // WFLY-1533 Underlying HTTP session may be invalid
            safelyDissociate(getSessionContext(), request);

            // Catch block is inside the activator method so that we're able to log the context
            conversationContextActivator.disassociateConversationContext(request);

            SessionHolder.clear();
        }
    }

    public boolean isConversationActivationSet() {
        return conversationActivationEnabled != null;
    }

    public void setConversationActivationEnabled(boolean conversationActivationEnabled) {
        this.conversationActivationEnabled = conversationActivationEnabled;
    }

    @Override
    public void cleanup() {
    }

    /**
     * Some Servlet containers fire HttpServletListeners for include requests (inner requests caused by calling the include method of RequestDispatcher). This
     * causes problems with context shut down as context manipulation is not reentrant. This method detects if this request is an included request or not.
     */
    private boolean isIncludedRequest(HttpServletRequest request) {
        return request.getAttribute(INCLUDE_HEADER) != null;
    }

    /**
     * Some Servlet containers fire HttpServletListeners for forward requests (inner requests caused by calling the forward method of RequestDispatcher). This
     * causes problems with context shut down as context manipulation is not reentrant. This method detects if this request is an forwarded request or not.
     */
    private boolean isForwardedRequest(HttpServletRequest request) {
        return request.getAttribute(FORWARD_HEADER) != null;
    }

    /**
     * The way servlet containers react to an exception that occurs in a {@link ServletRequestListener} differs among servlet listeners. In certain containers
     * the destroyed callback may be invoked multiple times, causing the latter invocations to fail as thread locals have already been unset. We use the
     * {@link #REQUEST_DESTROYED} flag to indicate that all further invocations of the
     * {@link ServletRequestListener#requestDestroyed(javax.servlet.ServletRequestEvent)} should be ignored by Weld.
     */
    private boolean isRequestDestroyed(HttpServletRequest request) {
        return request.getAttribute(REQUEST_DESTROYED) != null;
    }

    private  void safelyDissociate(BoundContext context, T storage) {
        try {
            context.dissociate(storage);
        } catch (Exception e) {
            ServletLogger.LOG.unableToDissociateContext(context, storage);
            ServletLogger.LOG.catchingDebug(e);
        }
    }

    private void safelyDeactivate(ManagedContext context, HttpServletRequest request) {
        try {
            context.deactivate();
        } catch (Exception e) {
            ServletLogger.LOG.unableToDeactivateContext(context, request);
            ServletLogger.LOG.catchingDebug(e);
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy