Package org.jboss.weld.servlet

Source Code of org.jboss.weld.servlet.HttpContextLifecycle$Counter

/*
* 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 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.api.Service;
import org.jboss.weld.context.cache.RequestScopedBeanCache;
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.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 {

    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<ServletContext> applicationInitializedEvent;
    private final FastEvent<ServletContext> applicationDestroyedEvent;
    private final FastEvent<HttpServletRequest> requestInitializedEvent;
    private final FastEvent<HttpServletRequest> requestDestroyedEvent;
    private final FastEvent<HttpSession> sessionInitializedEvent;
    private final FastEvent<HttpSession> sessionDestroyedEvent;

    private final ServletApiAbstraction servletApi;

    private final ServletContextService servletContextService;

    private final Container container;

    private static final ThreadLocal<Counter> nestedInvocationGuard = new ThreadLocal<HttpContextLifecycle.Counter>();
    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.applicationInitializedEvent = FastEvent.of(ServletContext.class, beanManager, InitializedLiteral.APPLICATION);
        this.applicationDestroyedEvent = FastEvent.of(ServletContext.class, beanManager, DestroyedLiteral.APPLICATION);
        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);
    }

    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);
        synchronized (container) {
            applicationInitializedEvent.fire(ctx);
        }
    }

    public void contextDestroyed(ServletContext ctx) {
        synchronized (container) {
            applicationDestroyedEvent.fire(ctx);
        }
    }

    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();
        RequestScopedBeanCache.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.<HttpRequestContextImpl> 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)) {
                getRequestContext().invalidate();
            }
            getRequestContext().deactivate();
            // fire @Destroyed(RequestScoped.class)
            requestDestroyedEvent.fire(request);
            getSessionContext().deactivate();
            // fire @Destroyed(SessionScoped.class)
            if (!getSessionContext().isValid()) {
                sessionDestroyedEvent.fire((HttpSession) request.getAttribute(HTTP_SESSION));
            }
        } finally {
            getRequestContext().dissociate(request);

            // WFLY-1533 Underlying HTTP session may be invalid
            try {
                getSessionContext().dissociate(request);
            } catch (Exception e) {
                ServletLogger.LOG.unableToDissociateContext(getSessionContext(), request);
                ServletLogger.LOG.catchingDebug(e);
            }
            // 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;
    }

    /**
     * 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;
    }

    @Override
    public void cleanup() {
    }
}
TOP

Related Classes of org.jboss.weld.servlet.HttpContextLifecycle$Counter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.