/* * Copyright 2001-2004 The Apache Software Foundation. * * 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.apache.axis.handlers; import org.apache.axis.AxisEngine; import org.apache.axis.AxisFault; import org.apache.axis.Constants; import org.apache.axis.Message; import org.apache.axis.MessageContext; import org.apache.axis.components.logger.LogFactory; import org.apache.axis.message.SOAPEnvelope; import org.apache.axis.message.SOAPHeaderElement; import org.apache.axis.session.SimpleSession; import org.apache.axis.utils.Messages; import org.apache.axis.utils.SessionUtils; import org.apache.commons.logging.Log; import javax.xml.namespace.QName; import javax.xml.rpc.server.ServiceLifecycle; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import java.util.Set; /** This handler uses SOAP headers to do simple session management. * *

Essentially, you install it on both the request and response chains of * your service, on both the client and the server side.

* *

ON THE SERVER:

* *

ON THE CLIENT:

* * *

SimpleSessions are "reaped" periodically via a very simplistic * mechanism. Each time the handler is invoke()d we check to see if more * than reapPeriodicity milliseconds have elapsed since the last * reap. If so, we walk the collection of active Sessions, and for each * one, if it hasn't been "touched" (i.e. had a getProperty() or setProperty() * performed) in longer than its timeout, we remove it from the collection.

* * @author Glen Daniels (gdaniels@apache.org) */ public class SimpleSessionHandler extends BasicHandler { protected static Log log = LogFactory.getLog(SimpleSessionHandler.class.getName()); public static final String SESSION_ID = "SimpleSession.id"; public static final String SESSION_NS = "http://xml.apache.org/axis/session"; public static final String SESSION_LOCALPART = "sessionID"; public static final QName sessionHeaderName = new QName(SESSION_NS, SESSION_LOCALPART); private Hashtable activeSessions = new Hashtable(); // Reap timed-out sessions on the first request after this many // seconds. private long reapPeriodicity = 30; private long lastReapTime = 0; // By default, sessions time out after 1 minute of inactivity (60 sec) private int defaultSessionTimeout = 60; /** * Process a MessageContext. */ public void invoke(MessageContext context) throws AxisFault { // Should we reap timed out sessions? long curTime = System.currentTimeMillis(); boolean reap = false; // Minimize synchronicity, just check in here, do reap later. synchronized (this) { if (curTime > lastReapTime + (reapPeriodicity * 1000)) { reap = true; lastReapTime = curTime; } } if (reap) { Set entries = activeSessions.entrySet(); Set victims = new HashSet(); Object key; Iterator i; for (i = entries.iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); key = entry.getKey(); SimpleSession session = (SimpleSession) entry.getValue(); if ((curTime - session.getLastAccessTime()) > (session.getTimeout() * 1000)) { log.debug(Messages.getMessage("timeout00", key.toString())); // Don't modify the hashtable while we're iterating. victims.add(key); } } // Now go remove all the victims we found during the iteration. for (i = victims.iterator(); i.hasNext();) { key = i.next(); SimpleSession session = (SimpleSession)activeSessions.get(key); activeSessions.remove(key); // For each victim, swing through the data looking for // ServiceLifecycle objects, and calling destroy() on them. // FIXME : This cleanup should probably happen on another // thread, as it might take a little while. Enumeration keys = session.getKeys(); while (keys != null && keys.hasMoreElements()) { String keystr = (String)keys.nextElement(); Object obj = session.get(keystr); if (obj != null && obj instanceof ServiceLifecycle) { ((ServiceLifecycle)obj).destroy(); } } } } if (context.isClient()) { doClient(context); } else { doServer(context); } } /** * Client side of processing. */ public void doClient(MessageContext context) throws AxisFault { if (context.getPastPivot()) { // This is a response. Check it for the session header. Message msg = context.getResponseMessage(); if (msg == null) return; SOAPEnvelope env = msg.getSOAPEnvelope(); SOAPHeaderElement header = env.getHeaderByName(SESSION_NS, SESSION_LOCALPART); if (header == null) return; // Got one! try { Long id = (Long)header. getValueAsType(Constants.XSD_LONG); // Store it away. AxisEngine engine = context.getAxisEngine(); engine.setOption(SESSION_ID, id); // Note that we processed this header! header.setProcessed(true); } catch (Exception e) { throw AxisFault.makeFault(e); } } else { AxisEngine engine = context.getAxisEngine(); Long id = (Long)engine.getOption(SESSION_ID); if (id == null) return; // We have a session ID, so insert the header Message msg = context.getRequestMessage(); if (msg == null) throw new AxisFault(Messages.getMessage("noRequest00")); SOAPEnvelope env = msg.getSOAPEnvelope(); SOAPHeaderElement header = new SOAPHeaderElement(SESSION_NS, SESSION_LOCALPART, id); env.addHeader(header); } } /** * Server side of processing. */ public void doServer(MessageContext context) throws AxisFault { if (context.getPastPivot()) { // This is a response. Add the session header if we have an // ID. Long id = (Long)context.getProperty(SESSION_ID); if (id == null) return; Message msg = context.getResponseMessage(); if (msg == null) return; SOAPEnvelope env = msg.getSOAPEnvelope(); SOAPHeaderElement header = new SOAPHeaderElement(SESSION_NS, SESSION_LOCALPART, id); env.addHeader(header); } else { // Request. Set up the session if we find the header. Message msg = context.getRequestMessage(); if (msg == null) throw new AxisFault(Messages.getMessage("noRequest00")); SOAPEnvelope env = msg.getSOAPEnvelope(); SOAPHeaderElement header = env.getHeaderByName(SESSION_NS, SESSION_LOCALPART); Long id; if (header != null) { // Got one! try { id = (Long)header. getValueAsType(Constants.XSD_LONG); } catch (Exception e) { throw AxisFault.makeFault(e); } } else { id = getNewSession(); } SimpleSession session = (SimpleSession)activeSessions.get(id); if (session == null) { // Must have timed out, get a new one. id = getNewSession(); session = (SimpleSession)activeSessions.get(id); } // This session is still active... session.touch(); // Store it away in the MessageContext. context.setSession(session); context.setProperty(SESSION_ID, id); } } /** * Generate a new session, register it, and return its ID. * * @return the new session's ID for later lookup. */ private synchronized Long getNewSession() { Long id = SessionUtils.generateSession(); SimpleSession session = new SimpleSession(); session.setTimeout(defaultSessionTimeout); activeSessions.put(id, session); return id; } /** * Set the reaper periodicity in SECONDS * * Convenience method for testing. * * !!! TODO: Should be able to set this via options on the Handler * or perhaps the engine. */ public void setReapPeriodicity(long reapTime) { reapPeriodicity = reapTime; } /** * Set the default session timeout in SECONDS * * Again, for testing. */ public void setDefaultSessionTimeout(int defaultSessionTimeout) { this.defaultSessionTimeout = defaultSessionTimeout; } }