// ========================================================================
// $Id: AbstractReplicatedStore.java,v 1.5 2004/06/22 16:23:44 jules_gosnell Exp $
// Copyright 2002-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// 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.mortbay.j2ee.session;
//----------------------------------------
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.jboss.logging.Logger;
//----------------------------------------
// implement scavenging
// implement setMaxInactiveInterval
// look for NYI/TODO
// this infrastructure could probably be used across JMS aswell -
// think about it...
/**
* Maintain synchronisation with other States representing the same session by
* publishing changes made to ourself and updating ourself according to
* notifications published by the other State objects.
*
* @author <a href="mailto:jules@mortbay.com">Jules Gosnell</a>
* @version 1.0
*/
abstract public class AbstractReplicatedStore extends AbstractStore
{
protected final static Logger _log = Logger
.getLogger(AbstractReplicatedStore.class);
protected ClassLoader _loader;
public AbstractReplicatedStore()
{
super();
_loader = Thread.currentThread().getContextClassLoader();
}
public ClassLoader getLoader()
{
return _loader;
}
public void setLoader(ClassLoader loader)
{
_loader = loader;
}
// ----------------------------------------
// tmp hack to prevent infinite loop
private final static ThreadLocal _replicating = new ThreadLocal();
public static boolean getReplicating()
{
return _replicating.get() == Boolean.TRUE;
}
public static void setReplicating(boolean replicating)
{
_replicating.set(replicating ? Boolean.TRUE : Boolean.FALSE);
}
// ----------------------------------------
public Object clone()
{
AbstractReplicatedStore ars = (AbstractReplicatedStore) super.clone();
ars.setLoader(getLoader());
return ars;
}
protected Map _sessions = new HashMap();
public int getSessions()
{
return _sessions.size();
}
// ----------------------------------------
// Store API - Store LifeCycle
public void destroy() // corresponds to ctor
{
_log.trace("destroying...");
_sessions.clear();
_sessions = null;
setManager(null);
super.destroy();
_log.trace("...destroyed");
}
// ----------------------------------------
// Store API - State LifeCycle
public State newState(String id, int maxInactiveInterval) throws Exception
{
long creationTime = System.currentTimeMillis();
if (!AbstractReplicatedStore.getReplicating())
{
Object[] argInstances = { id, new Long(creationTime),
new Integer(maxInactiveInterval),
new Integer(_actualMaxInactiveInterval) };
publish(null, CREATE_SESSION, argInstances);
}
createSession(id, creationTime, maxInactiveInterval,
_actualMaxInactiveInterval);
// if we get one - all we have to do is loadState - because we
// will have just created it...
return loadState(id);
}
public State loadState(String id)
{
// pull it out of our cache - if it is not there, it doesn't
// exist/hasn't been distributed...
Object tmp;
synchronized (_sessions)
{
tmp = _sessions.get(id);
}
return (State) tmp;
}
public void storeState(State state)
{
try
{
String id = state.getId();
synchronized (_sessions)
{
_sessions.put(id, state);
}
}
catch (Exception e)
{
_log.error("error storing session", e);
}
}
public void removeState(State state) throws Exception
{
String id = state.getId();
if (!AbstractReplicatedStore.getReplicating())
{
Object[] argInstances = { id };
publish(null, DESTROY_SESSION, argInstances);
}
destroySession(id);
}
// ----------------------------------------
// Store API - garbage collection
public void scavenge() throws Exception
{
_log.trace("starting distributed scavenging...");
Collection copy;
synchronized (_sessions)
{
copy = new ArrayList(_sessions.values());
}
if (_log.isTraceEnabled())
{
int n;
synchronized (_subscribers)
{
n = _subscribers.size();
}
_log.trace(copy.size() + " distributed sessions, " + n
+ " subscribers");
}
int n = 0;
for (Iterator i = copy.iterator(); i.hasNext();)
{
LocalState state = (LocalState) i.next();
if (!state.isValid(_scavengerExtraTime))
{
String id = state.getId();
if (_log.isDebugEnabled())
_log.debug("scavenging distributed session " + id);
destroySession(id);
i.remove();
++n;
}
}
if (_log.isTraceEnabled())
_log.trace("scavenged " + n + " distributed sessions");
_log.trace("...finished distributed scavenging");
}
// ----------------------------------------
// Store API - hacks... - NYI/TODO
public void passivateSession(StateAdaptor sa)
{
}
public boolean isDistributed()
{
return true;
}
// ----------------------------------------
// utils
public String getContextPath()
{
return getManager().getContextPath();
}
// ----------------------------------------
// change notification API
protected static Map _methodToInteger = new HashMap();
protected static Method[] _integerToMethod = new Method[8];
protected static Method CREATE_SESSION;
protected static Method DESTROY_SESSION;
protected static Method TOUCH_SESSIONS;
protected static Method SET_LAST_ACCESSED_TIME;
static
{
// this is absolutely horrible and will break if anyone changes
// the shape of the interface - but it is a quick, easy and
// efficient hack - so I am using it while I think of a better
// way...
try
{
int index = 0;
Method m = null;
// class methods...
m = CREATE_SESSION = AbstractReplicatedStore.class.getMethod(
"createSession", new Class[] { String.class, Long.TYPE,
Integer.TYPE, Integer.TYPE });
_integerToMethod[index] = m;
_methodToInteger.put(m.getName(), new Integer(index));
index++;
m = DESTROY_SESSION = AbstractReplicatedStore.class.getMethod(
"destroySession", new Class[] { String.class });
_integerToMethod[index] = m;
_methodToInteger.put(m.getName(), new Integer(index));
index++;
m = TOUCH_SESSIONS = AbstractReplicatedStore.class.getMethod(
"touchSessions", new Class[] { String[].class, Long.TYPE });
_integerToMethod[index] = m;
_methodToInteger.put(m.getName(), new Integer(index));
index++;
// instance methods...
m = SET_LAST_ACCESSED_TIME = State.class.getMethod(
"setLastAccessedTime", new Class[] { Long.TYPE });
_integerToMethod[index] = m;
_methodToInteger.put(m.getName(), new Integer(index));
index++;
m = State.class.getMethod("setMaxInactiveInterval",
new Class[] { Integer.TYPE });
_integerToMethod[index] = m;
_methodToInteger.put(m.getName(), new Integer(index));
index++;
m = State.class.getMethod("setAttribute", new Class[] {
String.class, Object.class, Boolean.TYPE });
_integerToMethod[index] = m;
_methodToInteger.put(m.getName(), new Integer(index));
index++;
m = State.class.getMethod("setAttributes",
new Class[] { Map.class });
_integerToMethod[index] = m;
_methodToInteger.put(m.getName(), new Integer(index));
index++;
m = State.class.getMethod("removeAttribute", new Class[] {
String.class, Boolean.TYPE });
_integerToMethod[index] = m;
_methodToInteger.put(m.getName(), new Integer(index));
index++;
}
catch (Exception e)
{
System.err
.println("AbstractReplicatedStore: something went wrong building dispatch tables");
e.printStackTrace(System.err);
}
}
abstract protected void publish(String id, Method method,
Object[] argInstances);
protected void dispatch(String id, Integer methodId, Object[] argInstances)
{
try
{
AbstractReplicatedStore.setReplicating(true);
Object target = null;
if (id == null)
{
// either this is a class method
target = this;
}
else
{
// or an instance method..
synchronized (_subscribers)
{
target = _subscribers.get(id);
}
}
try
{
Method method = _integerToMethod[methodId.intValue()];
if (target == null)
_log.warn("null target for " + method);
else
method.invoke(target, argInstances);
}
catch (Exception e)
{
_log
.error(
"this should never happen - code version mismatch ?",
e);
}
}
finally
{
AbstractReplicatedStore.setReplicating(false);
}
}
public void createSession(String id, long creationTime,
int maxInactiveInterval, int actualMaxInactiveInterval)
{
if (_log.isTraceEnabled())
_log.trace("creating replicated session: " + id);
State state = new LocalState(id, creationTime, maxInactiveInterval,
actualMaxInactiveInterval);
synchronized (_sessions)
{
_sessions.put(id, state);
}
if (AbstractReplicatedStore.getReplicating())
{
// _log.info("trying to promote replicated session");
getManager().getHttpSession(id); // should cause creation of corresponding InterceptorStack
}
}
public void destroySession(String id)
{
if (_log.isTraceEnabled())
_log.trace("destroying replicated session: " + id);
if (getManager().sessionExists(id))
getManager().destroySession(getManager().getHttpSession(id));
synchronized (_sessions)
{
_sessions.remove(id);
}
}
public void touchSessions(String[] ids, long time)
{
// _log.info("touching sessions...: "+ids);
for (int i = 0; i < ids.length; i++)
{
String id = ids[i];
Object target;
// I could synch the whole block. This is slower, but will not
// hold up everything else...
synchronized (_subscribers)
{
target = _subscribers.get(id);
}
try
{
((StateInterceptor) target).setLastAccessedTime(time);
}
catch (Exception e)
{
_log.warn("unable to touch session: " + id
+ " probably already removed");
}
}
}
//----------------------------------------
// subscription - Listener management...
protected Map _subscribers = new HashMap();
public void subscribe(String id, Object o)
{
_log.trace("subscribing: " + id);
synchronized (_subscribers)
{
_subscribers.put(id, o);
}
}
public void unsubscribe(String id)
{
_log.trace("unsubscribing: " + id);
synchronized (_subscribers)
{
_subscribers.remove(id);
}
}
}