/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.aspects.versioned;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.jboss.aop.InstanceAdvised;
import org.jboss.ha.framework.interfaces.HAPartition;
import org.jboss.ha.framework.interfaces.HAPartition.HAPartitionStateTransfer;
import org.jboss.ha.framework.server.HAPartitionLocator;
import org.jboss.logging.Logger;
import org.jboss.util.id.GUID;
/**
* This is a LRU cache. The TxCache itself is not transactional
* but any accesses to objects within the cache ARE transactional.
*/
public class DistributedTxCache implements HAPartitionStateTransfer
{
protected static final Class[] INSERT_TYPES = new Class[]{Object.class, Object.class};
protected static final Class[] REMOVE_TYPES = new Class[]{Object.class};
private static class LRUCache extends LinkedHashMap
{
private static final long serialVersionUID = -402696519285213913L;
private int maxSize;
public LRUCache(int max)
{
super(16, 0.75F, true);
this.maxSize = max;
}
protected boolean removeEldestEntry(Map.Entry eldest)
{
return this.size() > maxSize;
}
}
protected static Logger log = Logger.getLogger(DistributedTxCache.class);
protected long lockTimeout;
protected DistributedSynchronizationManager synchManager;
protected DistributedVersionManager versionManager;
protected String partitionName;
protected HAPartition partition;
protected String cacheName;
protected LRUCache cache = null;
protected int maxSize;
public DistributedTxCache(int maxSize, long lockTimeout, String cacheName)
{
this(maxSize, lockTimeout, cacheName, "DefaultPartition");
}
public DistributedTxCache(int maxSize, long lockTimeout, String cacheName, String pName)
{
this.lockTimeout = lockTimeout;
this.partitionName = pName;
this.maxSize = maxSize;
this.cacheName = "DistributedTxCache/" + cacheName;
}
// HAPartition.HAPartitionStateTransfer Implementation --------------------------------------------------------
/**
* FIXME Replace this with an SPI. Don't leak the ClusterPartitionMBean class.
*/
protected HAPartition findHAPartitionWithName (String name) throws Exception
{
return HAPartitionLocator.getHAPartitionLocator().getHAPartition(name, null);
}
public void create() throws Exception
{
this.partition = findHAPartitionWithName(partitionName);
//REVISIT: doesn't really buy us anything until JGroups synchronizes
// initial state correctly
//partition.subscribeToStateTransferEvents(cacheName, this);
//REVISIT AGAIN: Actually I talked to Bela about this. I can change the
//Clustering framework to do state transfer correctly
partition.registerRPCHandler(cacheName, this);
synchManager = new DistributedSynchronizationManager(cacheName, null, partition);
versionManager = new DistributedVersionManager(lockTimeout, synchManager);
synchManager.versionManager = versionManager;
synchManager.create();
}
public synchronized void start() throws Exception
{
synchManager.start();
pullState();
if (cache == null) cache = new LRUCache(maxSize);
}
protected void pullState() throws Exception
{
Object[] args = {};
List rsp = partition.callMethodOnCluster(cacheName, "getCurrentState", args, null, true);
if (rsp.size() > 0)
{
setCurrentState((Serializable)rsp.get(0));
}
}
public synchronized void _insert(Object key, Object obj)
{
cache.put(key, obj);
}
public void insert(Object key, Object obj) throws Exception
{
try
{
obj = versionManager.makeVersioned(obj);
if (versionManager.isVersioned(obj))
{
log.trace("Inserting versioned object");
obj = VersionManager.getGUID((InstanceAdvised)obj);
}
else
{
log.trace("Inserting a non-Versioned object");
}
Object[] args = {key, obj};
partition.callMethodOnCluster(cacheName, "_insert", args, INSERT_TYPES, false);
}
catch (Exception ex)
{
ex.printStackTrace();
throw ex;
}
}
public synchronized void _remove(Object key)
{
cache.remove(key);
}
public void remove(Object key)
{
Object[] args = {key};
try
{
partition.callMethodOnCluster(cacheName, "_remove", args, REMOVE_TYPES, false);
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
public synchronized void _flush()
{
cache.clear();
}
public void flush(Object key)
{
Object[] args = {};
try
{
partition.callMethodOnCluster(cacheName, "_flush", args, null, false);
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
public synchronized Object get(Object key)
{
Object obj = cache.get(key);
if (obj instanceof GUID)
{
GUID guid = (GUID)obj;
obj = synchManager.getObject(guid);
}
return obj;
}
public Serializable getCurrentState()
{
log.trace("getCurrentState called on cache");
return cache;
}
public void setCurrentState(Serializable newState)
{
log.trace("setCurrentState called on cache");
synchronized (this)
{
this.cache = (LRUCache)newState;
}
}
}