blob: c93473de0724128c6dafb18c0d0fc5244493587d [file] [log] [blame]
/*
* 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.
*
* <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>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>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@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;
}
}