1 /*
2  * JBoss, Home of Professional Open Source.
3  * Copyright 2014 Red Hat, Inc., and individual contributors
4  * as indicated by the @author tags.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */

18
19 package io.undertow.servlet.core;
20
21 import java.security.AccessController;
22 import java.util.HashSet;
23 import javax.servlet.ServletContext;
24 import javax.servlet.ServletException;
25 import javax.servlet.http.HttpSessionBindingEvent;
26 import javax.servlet.http.HttpSessionBindingListener;
27
28 import io.undertow.server.HttpServerExchange;
29 import io.undertow.server.session.Session;
30 import io.undertow.server.session.SessionListener;
31 import io.undertow.servlet.api.Deployment;
32 import io.undertow.servlet.api.ThreadSetupHandler;
33 import io.undertow.servlet.handlers.ServletRequestContext;
34 import io.undertow.servlet.spec.HttpSessionImpl;
35
36 /**
37  * Class that bridges between Undertow native session listeners and servlet ones.
38  *
39  * @author Stuart Douglas
40  */

41 public class SessionListenerBridge implements SessionListener {
42
43     public static final String IO_UNDERTOW = "io.undertow";
44     private final ApplicationListeners applicationListeners;
45     private final ServletContext servletContext;
46     private final ThreadSetupHandler.Action<Void, Session> destroyedAction;
47
48     public SessionListenerBridge(final Deployment deployment, final ApplicationListeners applicationListeners, final ServletContext servletContext) {
49         this.applicationListeners = applicationListeners;
50         this.servletContext = servletContext;
51         this.destroyedAction = deployment.createThreadSetupAction(new ThreadSetupHandler.Action<Void, Session>() {
52             @Override
53             public Void call(HttpServerExchange exchange, Session session) throws ServletException {
54                 doDestroy(session);
55                 return null;
56             }
57         });
58     }
59
60
61     @Override
62     public void sessionCreated(final Session session, final HttpServerExchange exchange) {
63         final HttpSessionImpl httpSession = SecurityActions.forSession(session, servletContext, true);
64         applicationListeners.sessionCreated(httpSession);
65     }
66
67     @Override
68     public void sessionDestroyed(final Session session, final HttpServerExchange exchange, final SessionDestroyedReason reason) {
69
70         if (reason == SessionDestroyedReason.TIMEOUT) {
71             try {
72                 //we need to perform thread setup actions
73                 destroyedAction.call(exchange, session);
74             } catch (Exception e) {
75                 throw new RuntimeException(e);
76             }
77         } else {
78             doDestroy(session);
79         }
80
81         ServletRequestContext current = SecurityActions.currentServletRequestContext();
82         Session underlying = null;
83         if (current != null && current.getSession() != null) {
84             if (System.getSecurityManager() == null) {
85                 underlying = current.getSession().getSession();
86             } else {
87                 underlying = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(current.getSession()));
88             }
89         }
90
91         if (current != null && underlying == session) {
92             current.setSession(null);
93         }
94     }
95
96     private void doDestroy(Session session) {
97         final HttpSessionImpl httpSession = SecurityActions.forSession(session, servletContext, false);
98         applicationListeners.sessionDestroyed(httpSession);
99         //we make a defensive copy here, as there is no guarantee that the underlying session map
100         //is a concurrent map, and as a result a concurrent modification exception may be thrown
101         HashSet<String> names = new HashSet<>(session.getAttributeNames());
102         for (String attribute : names) {
103             session.removeAttribute(attribute);
104         }
105     }
106
107     @Override
108     public void attributeAdded(final Session session, final String name, final Object value) {
109         if (name.startsWith(IO_UNDERTOW)) {
110             return;
111         }
112         final HttpSessionImpl httpSession = SecurityActions.forSession(session, servletContext, false);
113         applicationListeners.httpSessionAttributeAdded(httpSession, name, value);
114         if (value instanceof HttpSessionBindingListener) {
115             ((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(httpSession, name, value));
116         }
117     }
118
119     @Override
120     public void attributeUpdated(final Session session, final String name, final Object value, final Object old) {
121         if (name.startsWith(IO_UNDERTOW)) {
122             return;
123         }
124         final HttpSessionImpl httpSession = SecurityActions.forSession(session, servletContext, false);
125         if (old != value) {
126             if (old instanceof HttpSessionBindingListener) {
127                 ((HttpSessionBindingListener) old).valueUnbound(new HttpSessionBindingEvent(httpSession, name, old));
128             }
129             applicationListeners.httpSessionAttributeReplaced(httpSession, name, old);
130         }
131         if (value instanceof HttpSessionBindingListener) {
132             ((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(httpSession, name, value));
133         }
134     }
135
136     @Override
137     public void attributeRemoved(final Session session, final String name, final Object old) {
138         if (name.startsWith(IO_UNDERTOW)) {
139             return;
140         }
141         final HttpSessionImpl httpSession = SecurityActions.forSession(session, servletContext, false);
142         if (old != null) {
143             applicationListeners.httpSessionAttributeRemoved(httpSession, name, old);
144             if (old instanceof HttpSessionBindingListener) {
145                 ((HttpSessionBindingListener) old).valueUnbound(new HttpSessionBindingEvent(httpSession, name, old));
146             }
147         }
148     }
149
150     @Override
151     public void sessionIdChanged(Session session, String oldSessionId) {
152     }
153 }
154