/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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 io.undertow.servlet.core; import io.undertow.servlet.UndertowServletLogger; import javax.servlet.ServletContext; import javax.servlet.ServletContextAttributeEvent; import javax.servlet.ServletContextAttributeListener; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletRequestAttributeEvent; import javax.servlet.ServletRequestAttributeListener; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionIdListener; import javax.servlet.http.HttpSessionListener; import java.util.ArrayList; import java.util.List; import static io.undertow.servlet.core.ApplicationListeners.ListenerState.DECLARED_LISTENER; import static io.undertow.servlet.core.ApplicationListeners.ListenerState.PROGRAMATIC_LISTENER; /** * Class that is responsible for invoking application listeners. * <p> * This class does not perform any context setup, the context must be setup * before invoking this class. * <p> * Note that arrays are used instead of lists for performance reasons. * * @author Stuart Douglas */ public class ApplicationListeners implements Lifecycle { private static final ManagedListener[] EMPTY = {}; private static final Class[] LISTENER_CLASSES = {ServletContextListener.class, ServletContextAttributeListener.class, ServletRequestListener.class, ServletRequestAttributeListener.class, javax.servlet.http.HttpSessionListener.class, javax.servlet.http.HttpSessionAttributeListener.class, HttpSessionIdListener.class}; private static final ThreadLocal<ListenerState> IN_PROGRAMATIC_SC_LISTENER_INVOCATION = new ThreadLocal<ListenerState>() { @Override protected ListenerState initialValue() { return ListenerState.NO_LISTENER; } }; private ServletContext servletContext; private final List<ManagedListener> allListeners = new ArrayList<>(); private ManagedListener[] servletContextListeners; private ManagedListener[] servletContextAttributeListeners; private ManagedListener[] servletRequestListeners; private ManagedListener[] servletRequestAttributeListeners; private ManagedListener[] httpSessionListeners; private ManagedListener[] httpSessionAttributeListeners; private ManagedListener[] httpSessionIdListeners; private volatile boolean started = false; public ApplicationListeners(final List<ManagedListener> allListeners, final ServletContext servletContext) { this.servletContext = servletContext; servletContextListeners = EMPTY; servletContextAttributeListeners = EMPTY; servletRequestListeners = EMPTY; servletRequestAttributeListeners = EMPTY; httpSessionListeners = EMPTY; httpSessionAttributeListeners = EMPTY; httpSessionIdListeners = EMPTY; for (final ManagedListener listener : allListeners) { addListener(listener); } } public void addListener(final ManagedListener listener) { if (ServletContextListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { ManagedListener[] old = servletContextListeners; servletContextListeners = new ManagedListener[old.length + 1]; System.arraycopy(old, 0, servletContextListeners, 0, old.length); servletContextListeners[old.length] = listener; } if (ServletContextAttributeListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { ManagedListener[] old = servletContextAttributeListeners; servletContextAttributeListeners = new ManagedListener[old.length + 1]; System.arraycopy(old, 0, servletContextAttributeListeners, 0, old.length); servletContextAttributeListeners[old.length] = listener; } if (ServletRequestListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { ManagedListener[] old = servletRequestListeners; servletRequestListeners = new ManagedListener[old.length + 1]; System.arraycopy(old, 0, servletRequestListeners, 0, old.length); servletRequestListeners[old.length] = listener; } if (ServletRequestAttributeListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { ManagedListener[] old = servletRequestAttributeListeners; servletRequestAttributeListeners = new ManagedListener[old.length + 1]; System.arraycopy(old, 0, servletRequestAttributeListeners, 0, old.length); servletRequestAttributeListeners[old.length] = listener; } if (HttpSessionListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { ManagedListener[] old = httpSessionListeners; httpSessionListeners = new ManagedListener[old.length + 1]; System.arraycopy(old, 0, httpSessionListeners, 0, old.length); httpSessionListeners[old.length] = listener; } if (HttpSessionAttributeListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { ManagedListener[] old = httpSessionAttributeListeners; httpSessionAttributeListeners = new ManagedListener[old.length + 1]; System.arraycopy(old, 0, httpSessionAttributeListeners, 0, old.length); httpSessionAttributeListeners[old.length] = listener; } if (HttpSessionIdListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { ManagedListener[] old = httpSessionIdListeners; httpSessionIdListeners = new ManagedListener[old.length + 1]; System.arraycopy(old, 0, httpSessionIdListeners, 0, old.length); httpSessionIdListeners[old.length] = listener; } this.allListeners.add(listener); if(started) { try { listener.start(); } catch (ServletException e) { throw new RuntimeException(e); } } } public void start() throws ServletException { started = true; for (ManagedListener listener : allListeners) { listener.start(); } } public void stop() { if (started) { started = false; for (final ManagedListener listener : allListeners) { listener.stop(); } } } @Override public boolean isStarted() { return started; } public void contextInitialized() { if(!started) { return; } //new listeners can be added here, so we don't use an iterator final ServletContextEvent event = new ServletContextEvent(servletContext); for (int i = 0; i < servletContextListeners.length; ++i) { ManagedListener listener = servletContextListeners[i]; IN_PROGRAMATIC_SC_LISTENER_INVOCATION.set(listener.isProgramatic() ? PROGRAMATIC_LISTENER : DECLARED_LISTENER); try { this.<ServletContextListener>get(listener).contextInitialized(event); } finally { IN_PROGRAMATIC_SC_LISTENER_INVOCATION.remove(); } } } public void contextDestroyed() { if(!started) { return; } final ServletContextEvent event = new ServletContextEvent(servletContext); for (int i = servletContextListeners.length - 1; i >= 0; --i) { ManagedListener listener = servletContextListeners[i]; try { this.<ServletContextListener>get(listener).contextDestroyed(event); } catch (Exception e) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("contextDestroyed", listener.getListenerInfo().getListenerClass(), e); } } } public void servletContextAttributeAdded(final String name, final Object value) { if(!started) { return; } final ServletContextAttributeEvent sre = new ServletContextAttributeEvent(servletContext, name, value); for (int i = 0; i < servletContextAttributeListeners.length; ++i) { this.<ServletContextAttributeListener>get(servletContextAttributeListeners[i]).attributeAdded(sre); } } public void servletContextAttributeRemoved(final String name, final Object value) { if(!started) { return; } final ServletContextAttributeEvent sre = new ServletContextAttributeEvent(servletContext, name, value); for (int i = 0; i < servletContextAttributeListeners.length; ++i) { this.<ServletContextAttributeListener>get(servletContextAttributeListeners[i]).attributeRemoved(sre); } } public void servletContextAttributeReplaced(final String name, final Object value) { if(!started) { return; } final ServletContextAttributeEvent sre = new ServletContextAttributeEvent(servletContext, name, value); for (int i = 0; i < servletContextAttributeListeners.length; ++i) { this.<ServletContextAttributeListener>get(servletContextAttributeListeners[i]).attributeReplaced(sre); } } public void requestInitialized(final ServletRequest request) { if(!started) { return; } if(servletRequestListeners.length > 0) { final ServletRequestEvent sre = new ServletRequestEvent(servletContext, request); for (int i = 0; i < servletRequestListeners.length; ++i) { this.<ServletRequestListener>get(servletRequestListeners[i]).requestInitialized(sre); } } } public void requestDestroyed(final ServletRequest request) { if(!started) { return; } if(servletRequestListeners.length > 0) { final ServletRequestEvent sre = new ServletRequestEvent(servletContext, request); for (int i = servletRequestListeners.length - 1; i >= 0; --i) { ManagedListener listener = servletRequestListeners[i]; try { this.<ServletRequestListener>get(listener).requestDestroyed(sre); } catch (Exception e) { UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("requestDestroyed", listener.getListenerInfo().getListenerClass(), e); } } } } public void servletRequestAttributeAdded(final HttpServletRequest request, final String name, final Object value) { if(!started) { return; } final ServletRequestAttributeEvent sre = new ServletRequestAttributeEvent(servletContext, request, name, value); for (int i = 0; i < servletRequestAttributeListeners.length; ++i) { this.<ServletRequestAttributeListener>get(servletRequestAttributeListeners[i]).attributeAdded(sre); } } public void servletRequestAttributeRemoved(final HttpServletRequest request, final String name, final Object value) { if(!started) { return; } final ServletRequestAttributeEvent sre = new ServletRequestAttributeEvent(servletContext, request, name, value); for (int i = 0; i < servletRequestAttributeListeners.length; ++i) { this.<ServletRequestAttributeListener>get(servletRequestAttributeListeners[i]).attributeRemoved(sre); } } public void servletRequestAttributeReplaced(final HttpServletRequest request, final String name, final Object value) { if(!started) { return; } final ServletRequestAttributeEvent sre = new ServletRequestAttributeEvent(servletContext, request, name, value); for (int i = 0; i < servletRequestAttributeListeners.length; ++i) { this.<ServletRequestAttributeListener>get(servletRequestAttributeListeners[i]).attributeReplaced(sre); } } public void sessionCreated(final HttpSession session) { if(!started) { return; } final HttpSessionEvent sre = new HttpSessionEvent(session); for (int i = 0; i < httpSessionListeners.length; ++i) { this.<HttpSessionListener>get(httpSessionListeners[i]).sessionCreated(sre); } } public void sessionDestroyed(final HttpSession session) { if(!started) { return; } final HttpSessionEvent sre = new HttpSessionEvent(session); for (int i = httpSessionListeners.length - 1; i >= 0; --i) { ManagedListener listener = httpSessionListeners[i]; this.<HttpSessionListener>get(listener).sessionDestroyed(sre); } } public void httpSessionAttributeAdded(final HttpSession session, final String name, final Object value) { if(!started) { return; } final HttpSessionBindingEvent sre = new HttpSessionBindingEvent(session, name, value); for (int i = 0; i < httpSessionAttributeListeners.length; ++i) { this.<HttpSessionAttributeListener>get(httpSessionAttributeListeners[i]).attributeAdded(sre); } } public void httpSessionAttributeRemoved(final HttpSession session, final String name, final Object value) { if(!started) { return; } final HttpSessionBindingEvent sre = new HttpSessionBindingEvent(session, name, value); for (int i = 0; i < httpSessionAttributeListeners.length; ++i) { this.<HttpSessionAttributeListener>get(httpSessionAttributeListeners[i]).attributeRemoved(sre); } } public void httpSessionAttributeReplaced(final HttpSession session, final String name, final Object value) { if(!started) { return; } final HttpSessionBindingEvent sre = new HttpSessionBindingEvent(session, name, value); for (int i = 0; i < httpSessionAttributeListeners.length; ++i) { this.<HttpSessionAttributeListener>get(httpSessionAttributeListeners[i]).attributeReplaced(sre); } } public void httpSessionIdChanged(final HttpSession session, final String oldSessionId) { if(!started) { return; } final HttpSessionEvent sre = new HttpSessionEvent(session); for (int i = 0; i < httpSessionIdListeners.length; ++i) { this.<HttpSessionIdListener>get(httpSessionIdListeners[i]).sessionIdChanged(sre, oldSessionId); } } private <T> T get(final ManagedListener listener) { return (T) listener.instance(); } /** * returns true if this is in in a */ public static ListenerState listenerState() { return IN_PROGRAMATIC_SC_LISTENER_INVOCATION.get(); } /** * @param clazz The potential listener class * @return true if the provided class is a valid listener class */ public static boolean isListenerClass(final Class<?> clazz) { for (Class c : LISTENER_CLASSES) { if (c.isAssignableFrom(clazz)) { return true; } } return false; } public enum ListenerState { NO_LISTENER, DECLARED_LISTENER, PROGRAMATIC_LISTENER, } }