Source code: com/tripi/asp/AspSessionHandler.java
1 /**
2 * ArrowHead ASP Server
3 * This is a source file for the ArrowHead ASP Server - an 100% Java
4 * VBScript interpreter and ASP server.
5 *
6 * For more information, see http://www.tripi.com/arrowhead
7 *
8 * Copyright (C) 2002 Terence Haddock
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 */
25 package com.tripi.asp;
26
27 import org.apache.log4j.Category;
28
29 import javax.servlet.http.HttpSession;
30 import javax.servlet.http.HttpSessionListener;
31 import javax.servlet.http.HttpSessionEvent;
32 import javax.servlet.http.HttpSessionBindingEvent;
33
34 import java.util.Enumeration;
35 import java.util.HashMap;
36 import java.util.Iterator;
37 import java.util.Map;
38 import java.util.Set;
39
40 import java.io.IOException;
41 import java.io.Serializable;
42
43 /**
44 * This class handles synchronization between ASP Sessions and Servlet
45 * sessions for ASP to JSP interaction.
46 *
47 * @author Terence Haddock
48 */
49 public class AspSessionHandler implements HttpSessionListener
50 {
51 /**
52 * Debugging class
53 */
54 private Category DBG = Category.getInstance(AspSessionHandler.class);
55
56
57 /**
58 * Constructor.
59 */
60 public AspSessionHandler() {};
61
62 /**
63 * Global Session to AspCollection mapping.
64 */
65 private static HashMap sessionToAspCollection = new HashMap();
66
67 /**
68 * Session initialized.
69 * Note: I am assuming that this function is called in a thread-safe
70 * manner within a single session.
71 * @param e Event describing the initiazing of the session.
72 */
73 public void sessionCreated(HttpSessionEvent e)
74 {
75 HttpSession session = e.getSession();
76 if (DBG.isDebugEnabled())
77 {
78 DBG.debug("sessionCreated: " + e);
79 DBG.debug("Session: " + session.getId());
80 }
81
82 /* Create a hashtable to store the mappings to handle case changes */
83 HashMap caseMapping = new HashMap();
84
85 /* Store the case mappings into the session to handle passivation/activation */
86 session.setAttribute("ArrowHeadASP_CaseMappings", caseMapping);
87
88 /* Create the ArrowHead_Contents variable which will contain
89 an ASP-accessible mirror of the session contents */
90 AspCollection contents = new AspSynchronizedCollection(session, caseMapping);
91
92 /* Store this mapping to the hash map */
93 synchronized(sessionToAspCollection) {
94 sessionToAspCollection.put(session.getId(), contents);
95 }
96
97
98 if (DBG.isDebugEnabled()) DBG.debug("Before SESSION_ONSTART");
99
100 try {
101 GlobalScope.callSessionOnStart(session, contents);
102 } catch (Exception ex) {
103 DBG.error("Error calling SESSION_ONSTART", ex);
104 }
105
106 if (DBG.isDebugEnabled()) DBG.debug("After SESSION_ONSTART");
107 }
108
109 /**
110 * Session destroyed.
111 * @param e Event describing the destruction of the session.
112 */
113 public void sessionDestroyed(HttpSessionEvent e)
114 {
115 HttpSession session = e.getSession();
116 if (DBG.isDebugEnabled())
117 {
118 DBG.debug("sessionDestroyed: " + e);
119 DBG.debug("Session: " + session.getId());
120 }
121
122 if (DBG.isDebugEnabled()) DBG.debug("Before SESSION_ONEND");
123
124 AspSynchronizedCollection contents;
125 synchronized(sessionToAspCollection)
126 {
127 contents = (AspSynchronizedCollection)sessionToAspCollection.get(session.getId());
128 }
129 if (contents != null) {
130 contents.disconnectFromSession();
131
132 try {
133 GlobalScope.callSessionOnEnd(session, contents);
134 } catch (Exception ex) {
135 DBG.error("Error calling SESSION_ONEND", ex);
136 }
137 }
138
139 if (DBG.isDebugEnabled()) DBG.debug("After SESSION_ONEND");
140 synchronized(sessionToAspCollection)
141 {
142 sessionToAspCollection.remove(session.getId());
143 }
144 }
145
146 /**
147 * This function obtains the case mappings for the specified session.
148 */
149 static Map getCaseMappings(HttpSession session)
150 {
151 Map caseMapping;
152 synchronized(sessionToAspCollection) {
153 caseMapping = (Map)sessionToAspCollection.get("ArrowHeadASP_CaseMappings");
154 if (caseMapping == null) {
155 caseMapping = new HashMap();
156 sessionToAspCollection.put("ArrowHeadASP_CaseMappings", caseMapping);
157 }
158 }
159 return caseMapping;
160 }
161
162 /**
163 * This function obtains the ASP Contents for the specified session.
164 */
165 static AspCollection getContents(HttpSession session)
166 {
167 AspCollection collection;
168 synchronized(sessionToAspCollection) {
169 collection = (AspCollection)sessionToAspCollection.get(session.getId());
170 if (collection == null)
171 {
172 Map caseMapping = getCaseMappings(session);
173 collection = new AspSynchronizedCollection(session, caseMapping);
174 sessionToAspCollection.put(session.getId(), collection);
175 }
176 }
177 return collection;
178 }
179
180 /** This class helps synchronize Asp Session data with Servlet Session
181 * data.
182 */
183 public static class AspSynchronizedCollection extends AspCollection
184 implements SimpleMap
185 {
186 /**
187 * Debugging class
188 */
189 private static Category DBG = Category.getInstance(AspSynchronizedCollection.class);
190
191 /* The session this collection is attached to */
192 HttpSession session;
193
194 /* This variable is used to override the session synchronization */
195 boolean connected = true;
196
197 /* This hashtable is used to keep track of variable case */
198 Map caseMapping = null;
199
200 /**
201 * Constructor.
202 * @param session Session we are synchronized with.
203 */
204 AspSynchronizedCollection(HttpSession session, Map caseMapping)
205 {
206 this.session = session;
207 this.caseMapping = caseMapping;
208 /* Load all of the stored session data */
209 Set keySet = caseMapping.keySet();
210 for (Iterator i = keySet.iterator(); i.hasNext();)
211 {
212 String keyLower = (String)i.next();
213 String key = (String)caseMapping.get(keyLower);
214 Object value = session.getAttribute(key);
215 try {
216 if (value != null) {
217 internalPut(key, Types.coerceToNode(value));
218 }
219 } catch (AspException ex) {
220 DBG.error("Error during session synchronization", ex);
221 }
222 }
223 }
224
225 /**
226 * Obtains the value of data contained within the application
227 * object.
228 * @param obj Value to obtain.
229 * @return value of key obtained
230 * @throws AspException if an error occurs, most likely
231 * could not coerce value to string.
232 * @see SimpleMap#get(Object)
233 */
234 public synchronized Object get(Object key)
235 throws AspException
236 {
237 /* If we are not connected, get it from the collection */
238 if (!connected) {
239 return super.get(key);
240 }
241 /* If we are connected, first try the mapping */
242 String strKey = Types.coerceToString(key);
243 String strAspKey = (String)caseMapping.get(strKey.toLowerCase());
244 /* If we know the key, try the session */
245 if (strAspKey != null)
246 {
247 Object sessionValue = session.getAttribute(strAspKey);
248 if (sessionValue != null)
249 {
250 /* Found the value from the session */
251 return Types.coerceToNode(sessionValue);
252 } else {
253 return super.get(key);
254 }
255 }
256 /* We do not know about this key, let's look for it
257 in the session. First come, first serve. */
258 for (Enumeration e = session.getAttributeNames();e.hasMoreElements();)
259 {
260 String name = (String)e.nextElement();
261 if (name.equalsIgnoreCase(strKey)) {
262 /* Found it */
263 caseMapping.put(name.toLowerCase(), name);
264 return Types.coerceToNode(session.getAttribute(name));
265 }
266 }
267 /* This value is totally unknown. */
268 return Constants.undefinedValueNode;
269 }
270
271 /**
272 * Internal function to store a value without touching the HttpSession
273 * objects.
274 * @param key Key under which to store this item
275 * @param obj Object to store
276 * @throws AspException if an error occurs
277 */
278 public synchronized void internalPut(Object key, Object obj)
279 throws AspException
280 {
281 String strKey = Types.coerceToString(key);
282 caseMapping.put(strKey.toLowerCase(), strKey);
283 super.put(strKey, obj);
284 }
285
286 /**
287 * The put method adds an item to this collection
288 * @param key Key under which to store this item
289 * @param obj Object to store
290 * @throws AspException if an error occurs
291 */
292 public synchronized void put(Object key, Object obj) throws AspException
293 {
294 /* Store the value internally */
295 internalPut(key, obj);
296 if (connected)
297 {
298 /* Place the object into the session */
299 String str = Types.coerceToString(key);
300 /* De-reference wrapped Java objects */
301 Object value = obj;
302 while (value instanceof JavaObjectNode)
303 value = ((JavaObjectNode)value).getSubObject();
304 /* Only store the value if its serializable */
305 /* Map and List objects are not stored, because they can
306 contain non-serializable items */
307 if (value instanceof Serializable &&
308 !(value instanceof java.util.Map) &&
309 !(value instanceof java.util.List))
310 {
311 if (DBG.isDebugEnabled())
312 DBG.debug("Storing value " + str + " to session");
313 /* Store the value into the session */
314 session.setAttribute(str, value);
315 } else {
316 if (DBG.isDebugEnabled())
317 DBG.debug("Removing value " + str + " from session");
318 session.removeAttribute(str);
319 }
320 }
321 }
322
323 /**
324 * Internal function to remove the element without touching the
325 * HTTPSession.
326 * @param key Key to remove
327 * @throws AspException on error
328 */
329 public synchronized void internalRemove(Object key) throws AspException
330 {
331 String strKey = Types.coerceToString(key);
332 caseMapping.remove(strKey.toLowerCase());
333 super.remove(strKey);
334 }
335
336 /**
337 * Removes the element from this collection.
338 * @param key Key to remove
339 * @throws AspException on error
340 */
341 public synchronized void remove(Object key) throws AspException
342 {
343 internalRemove(key);
344 if (connected)
345 {
346 /* Get the key value */
347 String str = Types.coerceToString(key);
348 /* Remove all of the matching values from the session. */
349 for (Enumeration e = session.getAttributeNames();e.hasMoreElements();)
350 {
351 String name = (String)e.nextElement();
352 if (name.equalsIgnoreCase(str)) {
353 session.removeAttribute(name);
354 }
355 }
356 }
357 }
358
359 /**
360 * Removes all of the elements from this collection.
361 * @throws AspException on error
362 */
363 public synchronized void removeAll() throws AspException
364 {
365 super.removeAll();
366 if (connected)
367 {
368 /* Remove all values from the session. */
369 for (Enumeration e = session.getAttributeNames();e.hasMoreElements();)
370 {
371 String name = (String)e.nextElement();
372 session.removeAttribute(name);
373 }
374 }
375 }
376
377 /**
378 * Disconnect from the session. Used when sessions are closed so
379 * that setting/removing values to the session does not cause an
380 * invalidated session error.
381 */
382 public void disconnectFromSession()
383 {
384 connected = false;
385 }
386 }
387 }