package org.apache.jcs.auxiliary.remote.server;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import java.io.IOException;
import java.io.Serializable;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.server.Unreferenced;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes;
import org.apache.jcs.auxiliary.remote.behavior.IRemoteCacheListener;
import org.apache.jcs.auxiliary.remote.behavior.IRemoteCacheObserver;
import org.apache.jcs.auxiliary.remote.behavior.IRemoteCacheService;
import org.apache.jcs.auxiliary.remote.behavior.IRemoteCacheServiceAdmin;
import org.apache.jcs.auxiliary.remote.server.behavior.IRemoteCacheServerAttributes;
import org.apache.jcs.engine.CacheEventQueueFactory;
import org.apache.jcs.engine.CacheListeners;
import org.apache.jcs.engine.behavior.ICacheElement;
import org.apache.jcs.engine.behavior.ICacheEventQueue;
import org.apache.jcs.engine.behavior.ICacheListener;
import org.apache.jcs.engine.control.CompositeCache;
import org.apache.jcs.engine.control.CompositeCacheManager;
/**
* This class provides remote cache services. The remote cache server propagates
* events from local caches to other local caches. It can also store cached
* data, making it available to new clients.
* <p>
* Remote cache servers can be clustered. If the cache used by this remote cache
* is configured to use a remote cache of type cluster, the two remote caches
* will communicate with each other. Remote and put requests can be sent from
* one remote to another. If they are configured to broadcast such event to
* their client, then remove an puts can be sent to all locals in the cluster.
* <p>
* Get requests are made between clustered servers if AllowClusterGet is true. You can setup
* several clients to use one remote server and several to use another. The get
* locad will be distributed between the two servers. Since caches are usually
* high get and low put, this should allow you to scale.
*/
class RemoteCacheServer
extends UnicastRemoteObject
implements IRemoteCacheService, IRemoteCacheObserver, IRemoteCacheServiceAdmin, Unreferenced
{
private static final long serialVersionUID = -8072345435941473116L;
private final static Log log = LogFactory.getLog( RemoteCacheServer.class );
/** timing -- if we should record operation times. */
protected final static boolean timing = true;
private int puts = 0;
// Maps cache name to CacheListeners object.
// association of listeners (regions).
private final Hashtable cacheListenersMap = new Hashtable();
private final Hashtable clusterListenersMap = new Hashtable();
private CompositeCacheManager cacheManager;
// relates listener id with a type
private final Hashtable idTypeMap = new Hashtable();
// private transient int listenerId = 0;
private int[] listenerId = new int[1];
/** Configuration settings. */
protected IRemoteCacheServerAttributes rcsa;
/** The interval at which we will log updates. */
private int logInterval = 100;
/**
* Constructor for the RemoteCacheServer object. Thiks initializes the
* server with the values from the config file.
* <p>
* @param rcsa
* @throws RemoteException
* @exception IOException
*/
RemoteCacheServer( IRemoteCacheServerAttributes rcsa )
throws RemoteException
{
super( rcsa.getServicePort() );
this.rcsa = rcsa;
init( rcsa.getConfigFileName() );
}
/**
* Initialize the RMI Cache Server from a properties file.
* <p>
* @param prop
*/
private void init( String prop )
{
cacheManager = createCacheManager( prop );
// cacheManager would have created a number of ICache objects.
// Use these objects to set up the cacheListenersMap.
String[] list = cacheManager.getCacheNames();
for ( int i = 0; i < list.length; i++ )
{
String name = list[i];
cacheListenersMap.put( name, new CacheListeners( cacheManager.getCache( name ) ) );
}
}
/**
* Subclass can override this method to create the specific cache manager.
* <p>
* @param prop
* The anem of the configuration file.
* @return The cache hub configured with this configuration file.
*/
private CompositeCacheManager createCacheManager( String prop )
{
CompositeCacheManager hub = CompositeCacheManager.getUnconfiguredInstance();
if ( prop == null )
{
hub.configure( "/remote.cache.ccf" );
}
else
{
hub.configure( prop );
}
return hub;
}
/**
* Returns the cache listener for the specified cache. Creates the cache and
* the cache descriptor if they do not already exist.
* <p>
* @param cacheName
* @return The cacheListeners value
*/
protected CacheListeners getCacheListeners( String cacheName )
{
CacheListeners cacheListeners = (CacheListeners) cacheListenersMap.get( cacheName );
synchronized ( cacheListenersMap )
{
if ( cacheListeners == null )
{
cacheListeners = (CacheListeners) cacheListenersMap.get( cacheName );
if ( cacheListeners == null )
{
cacheListeners = new CacheListeners( cacheManager.getCache( cacheName ) );
cacheListenersMap.put( cacheName, cacheListeners );
}
}
}
return cacheListeners;
}
/**
* Gets the clusterListeners attribute of the RemoteCacheServer object.
* <p>
* @todo may be able to remove this
*
* @param cacheName
* @return The clusterListeners value
*/
protected CacheListeners getClusterListeners( String cacheName )
{
CacheListeners cacheListeners = (CacheListeners) clusterListenersMap.get( cacheName );
synchronized ( clusterListenersMap )
{
if ( cacheListeners == null )
{
cacheListeners = (CacheListeners) clusterListenersMap.get( cacheName );
if ( cacheListeners == null )
{
cacheListeners = new CacheListeners( cacheManager.getCache( cacheName ) );
clusterListenersMap.put( cacheName, cacheListeners );
}
}
}
return cacheListeners;
}
/**
* Puts a cache bean to the remote cache and notifies all listeners which
* <br>
* <ol>
* <li>have a different listener id than the originating host;
* <li>are currently subscribed to the related cache.
* </ol>
* <p>
* @param item
* @throws IOException
*
*/
public void put( ICacheElement item )
throws IOException
{
update( item );
}
/*
* (non-Javadoc)
*
* @see org.apache.jcs.engine.behavior.ICacheService#update(org.apache.jcs.engine.behavior.ICacheElement)
*/
public void update( ICacheElement item )
throws IOException
{
update( item, 0 );
}
/**
* An update can come from either a local cache's remote auxiliary, or it
* can come from a remote server. A remote server is considered a a source
* of type cluster.
* <p>
* If the update came from a cluster, then we should tell the cache manager
* that this was a remote put. This way, any lateral and remote auxiliaries
* configured for the region will not be updated. This is basically how a
* remote listener works when plugged into a local cache.
* <p>
* If the cluster is configured to keep local cluster consistency, then all
* listeners will be updated. This allows cluster server A to update cluster
* server B and then B to update its clients if it is told to keep local
* cluster consistency. Otherwise, server A will update server B and B will
* not tell its clients. If you cluster using lateral caches for instance,
* this is how it will work. Updates to a cluster node, will never get to
* the leavess. The remote cluster, with local cluster consistency, allows
* you to update leaves. This basically allows you to have a failover remote
* server.
* <p>
* Since currently a cluster will not try to get from other cluster servers,
* you can scale a bit with a cluster configuration. Puts and removes will
* be broadcasted to all clients, but the get load on a remote server can be
* reduced.
* <p>
* @param item
* @param requesterId
* @throws IOException
*/
public void update( ICacheElement item, long requesterId )
throws IOException
{
long start = 0;
if ( timing )
{
start = System.currentTimeMillis();
}
if ( log.isInfoEnabled() )
{
// not thread safe, but it doesn't have to be accurate
puts++;
if ( puts % logInterval == 0 )
{
log.info( "puts = " + puts );
}
}
if ( log.isDebugEnabled() )
{
log.debug( "In update, put [" + item.getKey() + "] in [" + item.getCacheName() + "]" );
}
try
{
CacheListeners cacheDesc = getCacheListeners( item.getCacheName() );
/* Object val = */item.getVal();
Integer remoteTypeL = (Integer) idTypeMap.get( new Long( requesterId ) );
if ( log.isDebugEnabled() )
{
log.debug( "In update, requesterId = [" + requesterId + "] remoteType = " + remoteTypeL );
}
boolean fromCluster = false;
if ( remoteTypeL != null && remoteTypeL.intValue() == IRemoteCacheAttributes.CLUSTER )
{
fromCluster = true;
}
// ordered cache item update and notification.
synchronized ( cacheDesc )
{
try
{
CompositeCache c = (CompositeCache) cacheDesc.cache;
// If the source of this request was not from a cluster,
// then consider it a local update. The cache manager will
// try to
// update all auxiliaries.
//
// This requires that two local caches not be connected to
// two clustered remote caches. The failover runner will
// have to make sure of this. ALos, the local cache needs
// avoid updating this source. Will need to pass the source
// id somehow. The remote cache should udate all local
// caches
// but not update the cluster source. Cluster remote caches
// should only be updated by the server and not the
// RemoteCache.
if ( fromCluster )
{
if ( log.isDebugEnabled() )
{
log.debug( "Put FROM cluster, NOT updating other auxiliaries for region. "
+ " requesterId [" + requesterId + "]" );
}
c.localUpdate( item );
}
else
{
if ( log.isDebugEnabled() )
{
log.debug( "Put NOT from cluster, updating other auxiliaries for region. "
+ " requesterId [" + requesterId + "]" );
}
c.update( item );
}
}
catch ( Exception ce )
{
// swallow
if ( log.isInfoEnabled() )
{
log.info( "Exception caught updating item. requesterId [" + requesterId + "] "
+ ce.getMessage() );
}
}
// UPDATE LOCALS IF A REQUEST COMES FROM A CLUSTER
// IF LOCAL CLUSTER CONSISTENCY IS CONFIGURED
if ( !fromCluster || ( fromCluster && rcsa.getLocalClusterConsistency() ) )
{
ICacheEventQueue[] qlist = getEventQList( cacheDesc, requesterId );
if ( qlist != null )
{
if ( log.isDebugEnabled() )
{
log.debug( "qlist.length = " + qlist.length );
}
for ( int i = 0; i < qlist.length; i++ )
{
qlist[i].addPutEvent( item );
}
}
else
{
if ( log.isDebugEnabled() )
{
log.debug( "q list is null" );
}
}
}
}
}
catch ( Exception e )
{
log.error( "Trouble in Update. requesterId [" + requesterId + "]", e );
}
// TODO use JAMON for timing
if ( timing )
{
long end = System.currentTimeMillis();
if ( log.isDebugEnabled() )
{
log.debug( "put took " + String.valueOf( end - start ) + " ms." );
}
}
return;
}
/**
* Gets the eventQList attribute of the RemoteCacheServer object. This
* returns the event queues stored in the cacheListeners object for a
* particuylar region, if the queue is not for this requester.
* <p>
* Basically, this makes sure that a request from a particular local cache,
* identified by its listener id, does not result in a call to that same
* listener.
* <p>
* @param cacheListeners
* @param requesterId
* @return The eventQList value
*/
private ICacheEventQueue[] getEventQList( CacheListeners cacheListeners, long requesterId )
{
ICacheEventQueue[] list = null;
synchronized ( cacheListeners.eventQMap )
{
list = (ICacheEventQueue[]) cacheListeners.eventQMap.values().toArray( new ICacheEventQueue[0] );
}
int count = 0;
// Set those not qualified to null; Count those qualified.
for ( int i = 0; i < list.length; i++ )
{
ICacheEventQueue q = list[i];
if ( q.isWorking() && q.getListenerId() != requesterId )
{
count++;
}
else
{
list[i] = null;
}
}
if ( count == list.length )
{
// All qualified.
return list;
}
// Returns only the qualified.
ICacheEventQueue[] qq = new ICacheEventQueue[count];
count = 0;
for ( int i = 0; i < list.length; i++ )
{
if ( list[i] != null )
{
qq[count++] = list[i];
}
}
return qq;
}
/**
* Returns a cache value from the specified remote cache; or null if the
* cache or key does not exist.
* <p>
* @param cacheName
* @param key
* @return ICacheElement
* @throws IOException
*/
public ICacheElement get( String cacheName, Serializable key )
throws IOException
{
return this.get( cacheName, key, 0 );
}
/**
* Returns a cache bean from the specified cache; or null if the key does
* not exist.
* <p>
* Adding the requestor id, allows the cache to determine the sournce of the
* get.
* <p>
* @param cacheName
* @param key
* @param requesterId
* @return ICacheElement
* @throws IOException
*/
public ICacheElement get( String cacheName, Serializable key, long requesterId )
throws IOException
{
Integer remoteTypeL = (Integer) idTypeMap.get( new Long( requesterId ) );
if ( log.isDebugEnabled() )
{
log.debug( "get [" + key + "] from cache [" + cacheName + "] requesterId = [" + requesterId + "] remoteType = "
+ remoteTypeL );
}
// Since a non-receiving remote cache client will not register a
// listener, it will not have a listener id assigned from the server. As
// such the remote server cannot determine if it is a cluster or a
// normal client. It will assume that it is a normal client.
boolean fromCluster = false;
if ( remoteTypeL != null && remoteTypeL.intValue() == IRemoteCacheAttributes.CLUSTER )
{
fromCluster = true;
}
CacheListeners cacheDesc = null;
try
{
cacheDesc = getCacheListeners( cacheName );
}
catch ( Exception e )
{
log.error( "Problem getting listeners.", e );
}
if ( cacheDesc == null )
{
return null;
}
CompositeCache c = (CompositeCache) cacheDesc.cache;
ICacheElement element = null;
// If we have a get come in from a client and we don't have the item
// locally, we will allow the cache to look in other non local sources,
// such as a remote cache or a lateral.
//
// Since remote servers never get from clients and clients never go
// remote from a remote call, this
// will not result in any loops.
//
// This is the only instance I can think of where we allow a remote get
// from a remote call. The purpose is to allow remote cache servers to
// talk to each other. If one goes down, you want it to be able to get
// data from those that were up when the failed server comes back o
// line.
if ( !fromCluster && this.rcsa.getAllowClusterGet() )
{
if ( log.isDebugEnabled() )
{
log.debug( "NonLocalGet. fromCluster [" + fromCluster + "] AllowClusterGet [" + this.rcsa.getAllowClusterGet() + "]" );
}
element = c.get( key );
}
else
{
// Gets from cluster type remote will end up here.
// Gets from all clients will end up here if allow cluster get is
// false.
if ( log.isDebugEnabled() )
{
log.debug( "LocalGet. fromCluster [" + fromCluster + "] AllowClusterGet [" + this.rcsa.getAllowClusterGet() + "]" );
}
element = c.localGet( key );
}
return element;
}
/**
* Gets the set of keys of objects currently in the group.
* <p>
* @param cacheName
* @param group
* @return A Set of group keys
*/
public Set getGroupKeys( String cacheName, String group )
{
CacheListeners cacheDesc = null;
try
{
cacheDesc = getCacheListeners( cacheName );
}
catch ( Exception e )
{
log.error( "Problem getting listeners.", e );
}
if ( cacheDesc == null )
{
return Collections.EMPTY_SET;
}
CompositeCache c = (CompositeCache) cacheDesc.cache;
return c.getGroupKeys( group );
}
/**
* Removes the given key from the specified remote cache. Defaults the
* listener id to 0.
* <p>
* @param cacheName
* @param key
* @throws IOException
*/
public void remove( String cacheName, Serializable key )
throws IOException
{
remove( cacheName, key, 0 );
}
/**
* Remove the key from the cache region and don't tell the source listener
* about it.
* <p>
* @param cacheName
* @param key
* @param requesterId
* @throws IOException
*/
public void remove( String cacheName, Serializable key, long requesterId )
throws IOException
{
if ( log.isDebugEnabled() )
{
log.debug( "remove [" + key + "] from cache [" + cacheName + "]" );
}
CacheListeners cacheDesc = (CacheListeners) cacheListenersMap.get( cacheName );
Integer remoteTypeL = (Integer) idTypeMap.get( new Long( requesterId ) );
boolean fromCluster = false;
if ( remoteTypeL != null && remoteTypeL.intValue() == IRemoteCacheAttributes.CLUSTER )
{
fromCluster = true;
}
if ( cacheDesc != null )
{
// best attempt to achieve ordered cache item removal and
// notification.
synchronized ( cacheDesc )
{
boolean removeSuccess = false;
// No need to notify if it was not cached.
CompositeCache c = (CompositeCache) cacheDesc.cache;
if ( fromCluster )
{
if ( log.isDebugEnabled() )
{
log.debug( "Remove FROM cluster, NOT updating other auxiliaries for region" );
}
removeSuccess = c.localRemove( key );
}
else
{
if ( log.isDebugEnabled() )
{
log.debug( "Remove NOT from cluster, updating other auxiliaries for region" );
}
removeSuccess = c.remove( key );
}
if ( log.isDebugEnabled() )
{
log.debug( "remove [" + key + "] from cache [" + cacheName + "] success (was it found) = "
+ removeSuccess );
}
// UPDATE LOCALS IF A REQUEST COMES FROM A CLUSTER
// IF LOCAL CLUSTER CONSISTENCY IS CONFIGURED
if ( !fromCluster || ( fromCluster && rcsa.getLocalClusterConsistency() ) )
{
ICacheEventQueue[] qlist = getEventQList( cacheDesc, requesterId );
for ( int i = 0; i < qlist.length; i++ )
{
qlist[i].addRemoveEvent( key );
}
}
}
}
return;
}
/**
* Remove all keys from the sepcified remote cache.
* <p>
* @param cacheName
* @throws IOException
*/
public void removeAll( String cacheName )
throws IOException
{
removeAll( cacheName, 0 );
}
/**
* Remove all keys from the specified remote cache.
* <p>
* @param cacheName
* @param requesterId
* @throws IOException
*/
public void removeAll( String cacheName, long requesterId )
throws IOException
{
CacheListeners cacheDesc = (CacheListeners) cacheListenersMap.get( cacheName );
Integer remoteTypeL = (Integer) idTypeMap.get( new Long( requesterId ) );
boolean fromCluster = false;
if ( remoteTypeL != null && remoteTypeL.intValue() == IRemoteCacheAttributes.CLUSTER )
{
fromCluster = true;
}
if ( cacheDesc != null )
{
// best attempt to achieve ordered cache item removal and
// notification.
synchronized ( cacheDesc )
{
// No need to broadcast, or notify if it was not cached.
CompositeCache c = (CompositeCache) cacheDesc.cache;
if ( fromCluster )
{
if ( log.isDebugEnabled() )
{
log.debug( "RemoveALL FROM cluster, NOT updating other auxiliaries for region" );
}
c.localRemoveAll();
}
else
{
if ( log.isDebugEnabled() )
{
log.debug( "RemoveALL NOT from cluster, updating other auxiliaries for region" );
}
c.removeAll();
}
// update registered listeners
if ( !fromCluster || ( fromCluster && rcsa.getLocalClusterConsistency() ) )
{
ICacheEventQueue[] qlist = getEventQList( cacheDesc, requesterId );
for ( int i = 0; i < qlist.length; i++ )
{
qlist[i].addRemoveAllEvent();
}
}
}
}
return;
}
/**
* How many put events have we received.
* <p>
* @return puts
*/
protected int getPutCount()
{
return puts;
}
/**
* Frees the specified remote cache.
* <p>
* @param cacheName
* @throws IOException
*/
public void dispose( String cacheName )
throws IOException
{
dispose( cacheName, 0 );
}
/**
* Frees the specified remote cache.
* <p>
* @param cacheName
* @param requesterId
* @throws IOException
*/
public void dispose( String cacheName, long requesterId )
throws IOException
{
if ( log.isInfoEnabled() )
{
log.info( "Dispose request received from listener [" + requesterId + "]" );
}
CacheListeners cacheDesc = (CacheListeners) cacheListenersMap.get( cacheName );
// this is dangerous
if ( cacheDesc != null )
{
// best attempt to achieve ordered free-cache-op and notification.
synchronized ( cacheDesc )
{
ICacheEventQueue[] qlist = getEventQList( cacheDesc, requesterId );
for ( int i = 0; i < qlist.length; i++ )
{
qlist[i].addDisposeEvent();
}
cacheManager.freeCache( cacheName );
}
}
return;
}
/**
* Frees all remote caches.
* <p>
* @throws IOException
*/
public void release()
throws IOException
{
synchronized ( cacheListenersMap )
{
for ( Enumeration en = cacheListenersMap.elements(); en.hasMoreElements(); )
{
CacheListeners cacheDesc = (CacheListeners) en.nextElement();
ICacheEventQueue[] qlist = getEventQList( cacheDesc, 0 );
for ( int i = 0; i < qlist.length; i++ )
{
qlist[i].addDisposeEvent();
}
}
cacheManager.release();
}
return;
}
/**
* Removes dead event queues. Should clean out deregistered listeners.
* <p>
* @param eventQMap
*/
private static void cleanupEventQMap( Map eventQMap )
{
synchronized ( eventQMap )
{
for ( Iterator itr = eventQMap.entrySet().iterator(); itr.hasNext(); )
{
Map.Entry e = (Map.Entry) itr.next();
ICacheEventQueue q = (ICacheEventQueue) e.getValue();
// this does not care if the q is alive (i.e. if
// there are active threads; it cares if the queue
// is working -- if it has not encoutnered errors
// above the failure threshhold
if ( !q.isWorking() )
{
itr.remove();
log.warn( "Cache event queue " + q + " is not working and removed from cache server." );
}
}
}
}
/**
* Subscribes to the specified remote cache.
* <p>
* If the client id is 0, then the remote cache server will increment it's
* local count and assign an id to the client.
* <p>
* @param cacheName
* the specified remote cache.
* @param listener
* object to notify for cache changes. must be synchronized since
* there are remote calls involved.
* @throws IOException
*/
public void addCacheListener( String cacheName, ICacheListener listener )
throws IOException
{
if ( cacheName == null || listener == null )
{
throw new IllegalArgumentException( "cacheName and listener must not be null" );
}
CacheListeners cacheDesc;
IRemoteCacheListener ircl = (IRemoteCacheListener) listener;
String listenerAddress = ircl.getLocalHostAddress();
int remoteType = ircl.getRemoteType();
if ( remoteType == IRemoteCacheAttributes.CLUSTER )
{
log.debug( "adding cluster listener, listenerAddress [" + listenerAddress + "]" );
cacheDesc = getClusterListeners( cacheName );
}
else
{
log.debug( "adding normal listener, listenerAddress [" + listenerAddress + "]" );
cacheDesc = getCacheListeners( cacheName );
}
Map eventQMap = cacheDesc.eventQMap;
cleanupEventQMap( eventQMap );
// synchronized ( listenerId )
synchronized ( ICacheListener.class )
{
long id = 0;
try
{
id = listener.getListenerId();
// clients problably shouldn't do this.
if ( id == 0 )
{
// must start at one so the next gets recognized
long listenerIdB = nextListenerId();
if ( log.isDebugEnabled() )
{
log.debug( "listener id=" + ( listenerIdB & 0xff ) + " addded for cache [" + cacheName
+ "], listenerAddress [" + listenerAddress + "]" );
}
listener.setListenerId( listenerIdB );
id = listenerIdB;
// in case it needs synchronization
if ( log.isInfoEnabled() )
{
log.info( "adding vm listener under new id = [" + listenerIdB + "], listenerAddress ["
+ listenerAddress + "]" );
}
}
else
{
if ( log.isInfoEnabled() )
{
log.info( "adding listener under existing id = [" + id + "], listenerAddress ["
+ listenerAddress + "]" );
}
// should confirm the the host is the same as we have on
// record, just in case a client has made a mistake.
}
// relate the type to an id
this.idTypeMap.put( new Long( id ), new Integer( remoteType ) );
}
catch ( IOException ioe )
{
log.error( "Problem setting listener id, listenerAddress [" + listenerAddress + "]", ioe );
}
CacheEventQueueFactory fact = new CacheEventQueueFactory();
ICacheEventQueue q = fact.createCacheEventQueue( listener, id, cacheName, rcsa.getEventQueuePoolName(),
rcsa.getEventQueueTypeFactoryCode() );
eventQMap.put( new Long( listener.getListenerId() ), q );
if ( log.isInfoEnabled() )
{
log.info( "Region " + cacheName + "'s listener size = " + cacheDesc.eventQMap.size() );
}
}
}
/**
* Subscribes to all remote caches.
* <p>
* @param listener
* The feature to be added to the CacheListener attribute
* @throws IOException
*/
public void addCacheListener( ICacheListener listener )
throws IOException
{
for ( Enumeration en = cacheListenersMap.keys(); en.hasMoreElements(); )
{
String cacheName = (String) en.nextElement();
addCacheListener( cacheName, listener );
if ( log.isDebugEnabled() )
{
log.debug( "Adding listener for cache [" + cacheName + "]" );
}
}
}
/**
* Unsubscribe this listener from this region. If the listener is
* registered, it will be removed from the event queue map list.
* <p>
* @param cacheName
* @param listenerId
*/
public void removeCacheListener( String cacheName, ICacheListener listener )
throws IOException
{
removeCacheListener( cacheName, listener.getListenerId() );
}
/**
* Unsubscribe this listener from this region. If the listener is
* registered, it will be removed from the event queue map list.
* <p>
* @param cacheName
* @param listenerId
*/
public void removeCacheListener( String cacheName, long listenerId )
{
if ( log.isInfoEnabled() )
{
log.info( "Removing listener for cache region = [" + cacheName + "] and listenerId [" + listenerId + "]" );
}
Integer remoteTypeL = (Integer) idTypeMap.get( new Long( listenerId ) );
boolean isClusterListener = false;
if ( remoteTypeL != null && remoteTypeL.intValue() == IRemoteCacheAttributes.CLUSTER )
{
isClusterListener = true;
}
CacheListeners cacheDesc = null;
if ( isClusterListener )
{
cacheDesc = getClusterListeners( cacheName );
}
else
{
cacheDesc = getCacheListeners( cacheName );
}
Map eventQMap = cacheDesc.eventQMap;
cleanupEventQMap( eventQMap );
ICacheEventQueue q = (ICacheEventQueue) eventQMap.remove( new Long( listenerId ) );
if ( q != null )
{
if ( log.isDebugEnabled() )
{
log.debug( "Found queue for cache region = [" + cacheName + "] and listenerId [" + listenerId + "]" );
}
q.destroy();
cleanupEventQMap( eventQMap );
}
else
{
if ( log.isDebugEnabled() )
{
log.debug( "Did not find queue for cache region = [" + cacheName + "] and listenerId [" + listenerId
+ "]" );
}
}
if ( log.isInfoEnabled() )
{
log.info( "After removing listener [" + listenerId + "] cache region " + cacheName + "'s listener size ["
+ cacheDesc.eventQMap.size() + "]" );
}
}
/**
* Unsubscribes from all remote caches.
* <p>
* @param listener
* @throws IOException
*/
public void removeCacheListener( ICacheListener listener )
throws IOException
{
for ( Enumeration en = cacheListenersMap.keys(); en.hasMoreElements(); )
{
String cacheName = (String) en.nextElement();
removeCacheListener( cacheName, listener );
if ( log.isInfoEnabled() )
{
log.info( "Removing listener for cache [" + cacheName + "]" );
}
}
return;
}
/**
* Shuts down the remote server.
* <p>
* @throws IOException
*/
public void shutdown()
throws IOException
{
RemoteCacheServerFactory.shutdownImpl( "", Registry.REGISTRY_PORT );
}
/**
* Shuts down a server at a particular host and port. Then it calls shutdown on the cache itself.
* <p>
* @param host
* @param port
* @throws IOException
*/
public void shutdown( String host, int port )
throws IOException
{
if ( log.isInfoEnabled() )
{
log.info( "Received shutdown request. Shutting down server." );
}
RemoteCacheServerFactory.shutdownImpl( host, port );
this.cacheManager.shutDown();
}
/**
* Called by the RMI runtime sometime after the runtime determines that the
* reference list, the list of clients referencing the remote object,
* becomes empty.
*/
// TODO: test out the DGC.
public void unreferenced()
{
if ( log.isInfoEnabled() )
{
log.info( "*** Server now unreferenced and subject to GC. ***" );
}
}
/**
* Returns the next generated listener id [0,255].
* <p>
* @return the listener id of a client. This should be unique for this
* server.
*/
private long nextListenerId()
{
long id = 0;
if ( listenerId[0] == Long.MAX_VALUE )
{
synchronized ( listenerId )
{
id = listenerId[0];
listenerId[0] = 0;
// TODO: record & check if the generated id is currently being
// used by a valid listener. Currently if the id wraps after
// Long.MAX_VALUE,
// we just assume it won't collide with an existing listener who
// is live.
}
}
else
{
synchronized ( listenerId )
{
id = ++listenerId[0];
}
}
return id;
}
/**
* Gets the stats attribute of the RemoteCacheServer object.
* <p>
* @return The stats value
* @throws IOException
*/
public String getStats()
throws IOException
{
return cacheManager.getStats();
}
}