SimpleSessionHandler.java |
/* * The Apache Software License, Version 1.1 * * * Copyright (c) 2001-2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Axis" and "Apache Software Foundation" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache", * nor may "Apache" appear in their name, without prior written * permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. */ package org.jboss.axis.handlers; import org.jboss.axis.AxisEngine; import org.jboss.axis.AxisFault; import org.jboss.axis.Constants; import org.jboss.axis.Message; import org.jboss.axis.MessageContext; import org.jboss.axis.message.SOAPEnvelopeAxisImpl; import org.jboss.axis.message.SOAPHeaderElementAxisImpl; import org.jboss.axis.session.SimpleSession; import org.jboss.axis.utils.Messages; import org.jboss.axis.utils.SessionUtils; import org.jboss.logging.Logger; 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. * <p/> * <p>Essentially, you install it on both the request and response chains of * your service, on both the client and the server side.</p> * <p/> * <p>ON THE SERVER:</p> * <ul> * <li>The REQUEST is checked for a session ID header. If present, we * look up the correct SimpleSession. If not, we create a new session. * In either case, we install the session into the MessageContext, and * put its ID in the SESSION_ID property. * <li>The RESPONSE gets a session ID header tacked on, assuming we found a * SESSION_ID property in the MessageContext. * </ul> * <p>ON THE CLIENT:</p> * <ul> * <li>The RESPONSE messages are checked for session ID headers. If present, * we pull the ID out and insert it into an option in the AxisClient. * This works because a given Call object is associated with a single * AxisClient. However, we might want to find a way to put it into the * Call object itself, which would make a little more sense. This would * mean being able to get to the Call from the MC, i.e. adding a getCall() * API (which would only work on the client side).... * <li>When REQUESTS are generated, we look to see if an ID option is present * in the AxisClient associated with the MessageContext. If so, we * insert a session ID header with the appropriate ID. * </ul> * <p/> * <p>SimpleSessions are "reaped" periodically via a very simplistic * mechanism. Each time the handler is invoke()d we check to see if more * than <b>reapPeriodicity</b> 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.</p> * * @author Glen Daniels (gdaniels@macromedia.com) */ public class SimpleSessionHandler extends BasicHandler { private static Logger log = Logger.getLogger(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; SOAPEnvelopeAxisImpl env = msg.getSOAPEnvelope(); SOAPHeaderElementAxisImpl 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")); SOAPEnvelopeAxisImpl env = msg.getSOAPEnvelope(); SOAPHeaderElementAxisImpl header = new SOAPHeaderElementAxisImpl(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; SOAPEnvelopeAxisImpl env = msg.getSOAPEnvelope(); SOAPHeaderElementAxisImpl header = new SOAPHeaderElementAxisImpl(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")); SOAPEnvelopeAxisImpl env = msg.getSOAPEnvelope(); SOAPHeaderElementAxisImpl 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 * <p/> * Convenience method for testing. * <p/> * !!! 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 * <p/> * Again, for testing. */ public void setDefaultSessionTimeout(int defaultSessionTimeout) { this.defaultSessionTimeout = defaultSessionTimeout; } }
SimpleSessionHandler.java |