Source code: org/apache/axis/handlers/SimpleSessionHandler.java
1 /*
2 * Copyright 2001-2004 The Apache Software Foundation.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.apache.axis.handlers;
18
19 import org.apache.axis.AxisEngine;
20 import org.apache.axis.AxisFault;
21 import org.apache.axis.Constants;
22 import org.apache.axis.Message;
23 import org.apache.axis.MessageContext;
24 import org.apache.axis.components.logger.LogFactory;
25 import org.apache.axis.message.SOAPEnvelope;
26 import org.apache.axis.message.SOAPHeaderElement;
27 import org.apache.axis.session.SimpleSession;
28 import org.apache.axis.utils.Messages;
29 import org.apache.axis.utils.SessionUtils;
30 import org.apache.commons.logging.Log;
31
32 import javax.xml.namespace.QName;
33 import javax.xml.rpc.server.ServiceLifecycle;
34 import java.util.Enumeration;
35 import java.util.HashSet;
36 import java.util.Hashtable;
37 import java.util.Iterator;
38 import java.util.Map;
39 import java.util.Set;
40
41 /** This handler uses SOAP headers to do simple session management.
42 *
43 * <p>Essentially, you install it on both the request and response chains of
44 * your service, on both the client and the server side.</p>
45 *
46 * <p>ON THE SERVER:</p>
47 * <ul>
48 * <li>The REQUEST is checked for a session ID header. If present, we
49 * look up the correct SimpleSession. If not, we create a new session.
50 * In either case, we install the session into the MessageContext, and
51 * put its ID in the SESSION_ID property.
52 * <li>The RESPONSE gets a session ID header tacked on, assuming we found a
53 * SESSION_ID property in the MessageContext.
54 * </ul>
55 * <p>ON THE CLIENT:</p>
56 * <ul>
57 * <li>The RESPONSE messages are checked for session ID headers. If present,
58 * we pull the ID out and insert it into an option in the AxisClient.
59 * This works because a given Call object is associated with a single
60 * AxisClient. However, we might want to find a way to put it into the
61 * Call object itself, which would make a little more sense. This would
62 * mean being able to get to the Call from the MC, i.e. adding a getCall()
63 * API (which would only work on the client side)....
64 * <li>When REQUESTS are generated, we look to see if an ID option is present
65 * in the AxisClient associated with the MessageContext. If so, we
66 * insert a session ID header with the appropriate ID.
67 * </ul>
68 *
69 * <p>SimpleSessions are "reaped" periodically via a very simplistic
70 * mechanism. Each time the handler is invoke()d we check to see if more
71 * than <b>reapPeriodicity</b> milliseconds have elapsed since the last
72 * reap. If so, we walk the collection of active Sessions, and for each
73 * one, if it hasn't been "touched" (i.e. had a getProperty() or setProperty()
74 * performed) in longer than its timeout, we remove it from the collection.</p>
75 *
76 * @author Glen Daniels (gdaniels@apache.org)
77 */
78 public class SimpleSessionHandler extends BasicHandler
79 {
80 protected static Log log =
81 LogFactory.getLog(SimpleSessionHandler.class.getName());
82
83 public static final String SESSION_ID = "SimpleSession.id";
84 public static final String SESSION_NS = "http://xml.apache.org/axis/session";
85 public static final String SESSION_LOCALPART = "sessionID";
86 public static final QName sessionHeaderName = new QName(SESSION_NS,
87 SESSION_LOCALPART);
88
89 private Hashtable activeSessions = new Hashtable();
90
91 // Reap timed-out sessions on the first request after this many
92 // seconds.
93 private long reapPeriodicity = 30;
94 private long lastReapTime = 0;
95
96 // By default, sessions time out after 1 minute of inactivity (60 sec)
97 private int defaultSessionTimeout = 60;
98
99 /**
100 * Process a MessageContext.
101 */
102 public void invoke(MessageContext context) throws AxisFault
103 {
104 // Should we reap timed out sessions?
105 long curTime = System.currentTimeMillis();
106 boolean reap = false;
107
108 // Minimize synchronicity, just check in here, do reap later.
109 synchronized (this) {
110 if (curTime > lastReapTime + (reapPeriodicity * 1000)) {
111 reap = true;
112 lastReapTime = curTime;
113 }
114 }
115
116 if (reap) {
117 Set entries = activeSessions.entrySet();
118 Set victims = new HashSet();
119 Object key;
120 Iterator i;
121 for (i = entries.iterator(); i.hasNext();) {
122 Map.Entry entry = (Map.Entry) i.next();
123 key = entry.getKey();
124 SimpleSession session = (SimpleSession) entry.getValue();
125 if ((curTime - session.getLastAccessTime()) >
126 (session.getTimeout() * 1000)) {
127 log.debug(Messages.getMessage("timeout00",
128 key.toString()));
129
130 // Don't modify the hashtable while we're iterating.
131 victims.add(key);
132 }
133 }
134
135 // Now go remove all the victims we found during the iteration.
136 for (i = victims.iterator(); i.hasNext();) {
137 key = i.next();
138 SimpleSession session = (SimpleSession)activeSessions.get(key);
139 activeSessions.remove(key);
140
141 // For each victim, swing through the data looking for
142 // ServiceLifecycle objects, and calling destroy() on them.
143 // FIXME : This cleanup should probably happen on another
144 // thread, as it might take a little while.
145 Enumeration keys = session.getKeys();
146 while (keys != null && keys.hasMoreElements()) {
147 String keystr = (String)keys.nextElement();
148 Object obj = session.get(keystr);
149 if (obj != null && obj instanceof ServiceLifecycle) {
150 ((ServiceLifecycle)obj).destroy();
151 }
152 }
153 }
154 }
155
156 if (context.isClient()) {
157 doClient(context);
158 } else {
159 doServer(context);
160 }
161 }
162
163 /**
164 * Client side of processing.
165 */
166 public void doClient(MessageContext context) throws AxisFault
167 {
168 if (context.getPastPivot()) {
169 // This is a response. Check it for the session header.
170 Message msg = context.getResponseMessage();
171 if (msg == null)
172 return;
173 SOAPEnvelope env = msg.getSOAPEnvelope();
174 SOAPHeaderElement header = env.getHeaderByName(SESSION_NS,
175 SESSION_LOCALPART);
176 if (header == null)
177 return;
178
179 // Got one!
180 try {
181 Long id = (Long)header.
182 getValueAsType(Constants.XSD_LONG);
183 // Store it away.
184 AxisEngine engine = context.getAxisEngine();
185 engine.setOption(SESSION_ID, id);
186 // Note that we processed this header!
187 header.setProcessed(true);
188 } catch (Exception e) {
189 throw AxisFault.makeFault(e);
190 }
191 } else {
192 AxisEngine engine = context.getAxisEngine();
193 Long id = (Long)engine.getOption(SESSION_ID);
194 if (id == null)
195 return;
196
197 // We have a session ID, so insert the header
198 Message msg = context.getRequestMessage();
199 if (msg == null)
200 throw new AxisFault(Messages.getMessage("noRequest00"));
201
202 SOAPEnvelope env = msg.getSOAPEnvelope();
203 SOAPHeaderElement header = new SOAPHeaderElement(SESSION_NS,
204 SESSION_LOCALPART,
205 id);
206 env.addHeader(header);
207 }
208 }
209
210 /**
211 * Server side of processing.
212 */
213 public void doServer(MessageContext context) throws AxisFault
214 {
215 if (context.getPastPivot()) {
216 // This is a response. Add the session header if we have an
217 // ID.
218 Long id = (Long)context.getProperty(SESSION_ID);
219 if (id == null)
220 return;
221
222 Message msg = context.getResponseMessage();
223 if (msg == null)
224 return;
225 SOAPEnvelope env = msg.getSOAPEnvelope();
226 SOAPHeaderElement header = new SOAPHeaderElement(SESSION_NS,
227 SESSION_LOCALPART,
228 id);
229 env.addHeader(header);
230 } else {
231 // Request. Set up the session if we find the header.
232 Message msg = context.getRequestMessage();
233 if (msg == null)
234 throw new AxisFault(Messages.getMessage("noRequest00"));
235
236 SOAPEnvelope env = msg.getSOAPEnvelope();
237 SOAPHeaderElement header = env.getHeaderByName(SESSION_NS,
238 SESSION_LOCALPART);
239 Long id;
240
241 if (header != null) {
242 // Got one!
243 try {
244 id = (Long)header.
245 getValueAsType(Constants.XSD_LONG);
246 } catch (Exception e) {
247 throw AxisFault.makeFault(e);
248 }
249 } else {
250 id = getNewSession();
251 }
252
253 SimpleSession session = (SimpleSession)activeSessions.get(id);
254 if (session == null) {
255 // Must have timed out, get a new one.
256 id = getNewSession();
257 session = (SimpleSession)activeSessions.get(id);
258 }
259
260 // This session is still active...
261 session.touch();
262
263 // Store it away in the MessageContext.
264 context.setSession(session);
265 context.setProperty(SESSION_ID, id);
266 }
267 }
268
269 /**
270 * Generate a new session, register it, and return its ID.
271 *
272 * @return the new session's ID for later lookup.
273 */
274 private synchronized Long getNewSession()
275 {
276 Long id = SessionUtils.generateSession();
277 SimpleSession session = new SimpleSession();
278 session.setTimeout(defaultSessionTimeout);
279 activeSessions.put(id, session);
280 return id;
281 }
282
283 /**
284 * Set the reaper periodicity in SECONDS
285 *
286 * Convenience method for testing.
287 *
288 * !!! TODO: Should be able to set this via options on the Handler
289 * or perhaps the engine.
290 */
291 public void setReapPeriodicity(long reapTime)
292 {
293 reapPeriodicity = reapTime;
294 }
295
296 /**
297 * Set the default session timeout in SECONDS
298 *
299 * Again, for testing.
300 */
301 public void setDefaultSessionTimeout(int defaultSessionTimeout) {
302 this.defaultSessionTimeout = defaultSessionTimeout;
303 }
304 }