package org.jboss.cache;
import net.jcip.annotations.ThreadSafe;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import static org.jboss.cache.Region.Type.*;
import org.jboss.cache.buddyreplication.BuddyManager;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.config.ConfigurationException;
import org.jboss.cache.config.EvictionConfig;
import org.jboss.cache.config.EvictionPolicyConfig;
import org.jboss.cache.config.EvictionRegionConfig;
import org.jboss.cache.eviction.EvictionTimerTask;
import org.jboss.cache.eviction.RegionNameConflictException;
import org.jboss.cache.factories.annotations.Destroy;
import org.jboss.cache.factories.annotations.Inject;
import org.jboss.cache.factories.annotations.Start;
import org.jboss.cache.factories.annotations.Stop;
import org.jboss.cache.lock.NodeLock;
import org.jgroups.Address;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Manages multiple {@link Region}s for a Cache instance.
*
* @author <a href="mailto:manik@jboss.org">Manik Surtani</a>
* @since 2.0.0
*/
@ThreadSafe
public class RegionManager
{
/**
* The default region used in XML configuration files when defining eviction policies. Any
* eviction settings bound under this 'default' Fqn is appplied to {@link org.jboss.cache.Fqn#ROOT} internally so
* any region that is not explicitly defined comes under the settings defined for this default.
*/
public static final Fqn<?> DEFAULT_REGION = new Fqn<String>("_default_");
/**
* A registry of regions that have been defined.
*/
private Map<Fqn, Region> regionsRegistry = new ConcurrentHashMap<Fqn, Region>();
private boolean defaultInactive;
private Log log = LogFactory.getLog(RegionManager.class);
private CacheSPI cache;
private boolean usingEvictions;
private EvictionConfig evictionConfig;
private EvictionTimerTask evictionTimerTask = new EvictionTimerTask();
protected final Set<Fqn> activationChangeNodes = Collections.synchronizedSet(new HashSet<Fqn>());
protected Configuration configuration;
protected RPCManager rpcManager;
@Inject
void injectDependencies(CacheSPI cache, Configuration configuration, RPCManager rpcManager)
{
this.cache = cache;
this.rpcManager = rpcManager;
this.configuration = configuration;
}
@Start
protected void start()
{
log.trace("Starting region manager");
if (configuration.getEvictionConfig() != null
&& configuration.getEvictionConfig().isValidConfig())
{
// validate individual region configs now
for (EvictionRegionConfig erc : configuration.getEvictionConfig().getEvictionRegionConfigs())
{
EvictionPolicyConfig epc = erc.getEvictionPolicyConfig();
if (epc != null) epc.validate();
}
setEvictionConfig(configuration.getEvictionConfig());
setUsingEvictions(true);
}
else
{
setUsingEvictions(false);
log.debug("Not using an EvictionPolicy");
}
setDefaultInactive(configuration.isInactiveOnStartup());
}
@Stop
protected void stop()
{
if (isUsingEvictions()) stopEvictionThread();
}
@Destroy
protected void destroy()
{
regionsRegistry.clear();
activationChangeNodes.clear();
}
/**
* @return true if evictions are being processed.
*/
public boolean isUsingEvictions()
{
return usingEvictions;
}
/**
* @return true if replication is by default inactive for new {@link Region}s.
*/
public boolean isDefaultInactive()
{
return defaultInactive;
}
/**
* Sets if replication for new {@link Region}s is by default inactive.
*/
public void setDefaultInactive(boolean defaultInactive)
{
this.defaultInactive = defaultInactive;
Region defaultRegion = regionsRegistry.get(Fqn.ROOT);
if (defaultRegion != null) defaultRegion.setActive(!defaultInactive);
}
/**
* Helper utility that checks for a {@link ClassLoader} registered for the
* given {@link Fqn}, and if found sets it as the TCCL. If the given Fqn is
* under the _BUDDY_BACKUP_ region, the equivalent region in the main
* cache is used to find the {@link ClassLoader}.
*
* @param fqn Fqn pointing to a region for which a special classloader
* may have been registered.
*/
public void setContextClassLoaderAsCurrent(Fqn fqn)
{
if (fqn.isChildOf(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN))
{
if (fqn.size() <= 2)
{
fqn = Fqn.ROOT;
}
else
{
fqn = fqn.getSubFqn(2, fqn.size());
}
}
Region region = getRegion(fqn, false);
ClassLoader regionCL = (region == null) ? null : region.getClassLoader();
if (regionCL != null)
{
Thread.currentThread().setContextClassLoader(regionCL);
}
}
/**
* Returns a region by {@link Fqn}, creating it optionally if absent. If the region does not exist and <tt>createIfAbsent</tt>
* is <tt>false</tt>, a parent region which may apply to the {@link Fqn} is sought.
*/
public Region getRegion(Fqn fqn, boolean createIfAbsent)
{
return getRegion(fqn, ANY, createIfAbsent);
}
/**
* An overloaded form of {@link #getRegion(Fqn,boolean)} that takes an additional {@link org.jboss.cache.Region.Type}
* parameter to force regions of a specific type.
*
* @see org.jboss.cache.Region.Type
*/
public Region getRegion(Fqn fqn, Region.Type type, boolean createIfAbsent)
{
if (log.isTraceEnabled()) log.trace("Contents of RegionsRegistry: " + regionsRegistry);
Fqn fqnToUse = fqn;
if (DEFAULT_REGION.equals(fqnToUse)) fqnToUse = Fqn.ROOT;
// first see if a region for this specific Fqn exists
if (regionsRegistry.containsKey(fqnToUse))
{
Region r = regionsRegistry.get(fqnToUse);
// this is a very poor way of telling whether a region is a marshalling one or an eviction one. :-(
// mandates that class loaders be registered for marshalling regions.
if (type == ANY
|| (type == MARSHALLING && r.getClassLoader() != null)
|| (type == EVICTION && r.getEvictionPolicyConfig() != null))
{
return r;
}
}
// if not, attempt to create one ...
if (createIfAbsent)
{
Region r = new RegionImpl(fqnToUse, this);
regionsRegistry.put(fqnToUse, r);
if (type == MARSHALLING)
{
// insert current class loader into region so at least it is recognised as a marshalling region
r.registerContextClassLoader(getClass().getClassLoader());
}
return r;
}
// else try and find a parent which has a defined region, may return null if nothing is defined.
Region nextBestThing = null;
Fqn nextFqn = fqnToUse;
while (nextBestThing == null)
{
nextFqn = nextFqn.getParent();
if (regionsRegistry.containsKey(nextFqn))
{
Region r = regionsRegistry.get(nextFqn);
if (log.isTraceEnabled()) log.trace("Trying next region " + nextFqn + " and got " + r);
// this is a very poor way of telling whether a region is a marshalling one or an eviction one. :-(
// mandates that class loaders be registered for marshalling regions.
if (type == ANY
|| (type == MARSHALLING && r.getClassLoader() != null)
|| (type == EVICTION && r.getEvictionPolicyConfig() != null))
{
nextBestThing = r;
}
}
if (nextFqn.isRoot()) break;
}
// test if the default region has been defined. If not, and if the request
// is for an eviction region, return null
if (type == EVICTION && nextBestThing != null && nextBestThing.getFqn().isRoot() && !regionsRegistry.containsKey(Fqn.ROOT))
{
log.trace("No default eviction region; returning null");
nextBestThing = null;
}
return nextBestThing;
}
/**
* Returns a region using Fqn.fromString(fqn), calling {@link #getRegion(Fqn,boolean)}
*
* @param fqn
* @param createIfAbsent
* @see #getRegion(Fqn,boolean)
*/
public Region getRegion(String fqn, boolean createIfAbsent)
{
return getRegion(Fqn.fromString(fqn), createIfAbsent);
}
/**
* Removes a {@link org.jboss.cache.Region} identified by the given fqn.
*
* @param fqn fqn of the region to remove
* @return true if such a region existed and was removed.
*/
public boolean removeRegion(Fqn fqn)
{
Region r = regionsRegistry.remove(fqn);
if (r == null) return false;
if (isUsingEvictions() && r.getEvictionPolicy() != null)
{
evictionTimerTask.removeRegionToProcess(r);
}
return true;
}
/**
* @return the eviction timer task object associated with this Region Manager.
*/
protected EvictionTimerTask getEvictionTimerTask()
{
return evictionTimerTask;
}
/**
* Activates unmarshalling of replication messages for the region
* rooted in the given Fqn.
* <p/>
* <strong>NOTE:</strong> This method will cause the creation of a node
* in the local cache at <code>subtreeFqn</code> whether or not that
* node exists anywhere else in the cluster. If the node does not exist
* elsewhere, the local node will be empty. The creation of this node will
* not be replicated.
* <p/>
*
* @param fqn representing the region to be activated.
* @throws RegionNotEmptyException if the node <code>fqn</code>
* exists and already has either data or children
*/
public void activate(Fqn fqn) throws RegionNotEmptyException
{
activate(fqn, false);
}
/**
* Attempts to activate a given region rooted at a given Fqn, similar to {@link #activate(Fqn)} except
* that if the fqn is currently already in use (probably already been activated) this method is a no-op.
*
* @param fqn which represents the region to activate
*/
public void activateIfEmpty(Fqn fqn)
{
activate(fqn, true);
}
private void activate(Fqn fqn, boolean suppressRegionNotEmptyException)
{
try
{
if (log.isTraceEnabled()) log.trace("Activating region " + fqn);
Region r = getRegion(fqn, false);
if (r != null)
{
if (!defaultInactive && r.getClassLoader() == null)
{
// This region's state will no match that of a non-existent one
// So, there is no reason to keep this region any more
// (Brian) We shouldn't do this anymore; now outside code
// can have a ref to the region!!
removeRegion(fqn);
}
else
{
//r.activate();
r.setStatus(Region.Status.ACTIVATING);
if (configuration.isFetchInMemoryState())
{
activateRegion(r.getFqn(), suppressRegionNotEmptyException);
}
r.setActive(true);
}
}
else if (defaultInactive)
{
// "Active" region is not the default, so create a region
r = getRegion(fqn, true);
// FIXME - persistent state transfer counts too!
r.setStatus(Region.Status.ACTIVATING);
if (configuration.isFetchInMemoryState())
{
activateRegion(r.getFqn(), suppressRegionNotEmptyException);
}
r.setActive(true);
}
}
catch (RuntimeException re)
{
throw re;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
/**
* Causes the cache to transfer state for the subtree rooted at
* <code>subtreeFqn</code> and to begin accepting replication messages
* for that subtree.
* <p/>
* <strong>NOTE:</strong> This method will cause the creation of a node
* in the local cache at <code>subtreeFqn</code> whether or not that
* node exists anywhere else in the cluster. If the node does not exist
* elsewhere, the local node will be empty. The creation of this node will
* not be replicated.
*
* @param fqn Fqn string indicating the uppermost node in the
* portion of the cache that should be activated.
* @throws RegionNotEmptyException if the node <code>subtreeFqn</code>
* exists and has either data or children
*/
private void activateRegion(Fqn fqn, boolean suppressRegionNotEmptyException)
{
// Check whether the node already exists and has data
Node subtreeRoot = cache.peek(fqn, false, false);
/*
* Commented out on Nov 16,2006 Manik&Vladimir
*
* if (!(cache.isNodeEmpty(subtreeRoot)))
{
throw new RegionNotEmptyException("Node " + subtreeRoot.getFqn() + " already exists and is not empty");
}*/
if (isActivatingDeactivating(fqn))
{
throw new CacheException("Region " + subtreeRoot.getFqn() + " is already being activated/deactivated");
}
if (log.isDebugEnabled())
{
log.debug("activating " + fqn);
}
try
{
// Add this fqn to the set of those we are activating
// so calls to _getState for the fqn can return quickly
activationChangeNodes.add(fqn);
BuddyManager buddyManager = cache.getBuddyManager();
// Request partial state from the cluster and integrate it
if (buddyManager == null)
{
// Get the state from any node that has it and put it
// in the main cache
if (subtreeRoot == null)
{
// We'll update this node with the state we receive
// need to obtain all necessary locks.
cache.getInvocationContext().getOptionOverrides().setCacheModeLocal(true);
subtreeRoot = cache.getRoot().addChild(fqn);
cache.getInvocationContext().getOptionOverrides().setCacheModeLocal(false);
}
List<Address> members = cache.getMembers();
// Don't bother trying to fetch state if we are in LOCAL mode
if (members != null && !members.isEmpty())
rpcManager.fetchPartialState(members, subtreeRoot.getFqn());
}
else if (!BuddyManager.isBackupFqn(fqn))
{
// Get the state from each DataOwner and integrate in their
// respective buddy backup cache
List<Address> buddies = buddyManager.getBackupDataOwners();
for (Address buddy : buddies)
{
List<Address> sources = new ArrayList<Address>(1);
if (!cache.getMembers().contains(buddy))
continue;
sources.add(buddy);
Fqn buddyRoot = BuddyManager.getBackupFqn(buddy, fqn);
subtreeRoot = cache.peek(buddyRoot, false, false);
if (subtreeRoot == null)
{
// We'll update this node with the state we receive
// need to obtain all necessary locks.
// needs to be a LOCAL call!
cache.getInvocationContext().getOptionOverrides().setCacheModeLocal(true);
subtreeRoot = cache.getRoot().addChild(buddyRoot);
cache.getInvocationContext().getOptionOverrides().setCacheModeLocal(false);
}
rpcManager.fetchPartialState(sources, fqn, subtreeRoot.getFqn());
}
}
else
{
log.info("Attempting to activate a backup region. Not attempting to retrieve any state as this will be pushed.");
}
}
catch (Throwable t)
{
log.error("failed to activate " + fqn, t);
// "Re-deactivate" the region
try
{
inactivateRegion(fqn);
}
catch (Exception e)
{
log.error("failed inactivating " + fqn, e);
// just swallow this one and throw the first one
}
// Throw the exception on, wrapping if necessary
if (t instanceof RegionNotEmptyException)
{
if (!suppressRegionNotEmptyException) throw (RegionNotEmptyException) t;
}
else if (t instanceof CacheException)
{
throw (CacheException) t;
}
else
{
throw new CacheException(t.getClass().getName() + " " +
t.getLocalizedMessage(), t);
}
}
finally
{
activationChangeNodes.remove(fqn);
}
}
/**
* Convenienve method. If the region defined by fqn does not exist, {@link #isDefaultInactive()} is returned, otherwise
* !{@link Region#isActive()} is returned.
*
* @param fqn fqn to test
* @return true if inactive
*/
public boolean isInactive(Fqn fqn)
{
Region region = getRegion(fqn, false);
return region == null ? defaultInactive : !region.isActive();
}
/**
* Causes the cache to stop accepting replication events for the subtree
* rooted at <code>subtreeFqn</code> and evict all nodes in that subtree.
* <p/>
* This is legacy code and should not be called directly. This is a private method for now and will be refactored out.
* You should be using {@link #activate(Fqn)} and {@link #deactivate(Fqn)}
* <p/>
*
* @param fqn Fqn string indicating the uppermost node in the
* portion of the cache that should be activated.
* @throws RegionNameConflictException if <code>subtreeFqn</code> indicates
* a node that is part of another
* subtree that is being specially
* managed (either by activate/inactiveRegion()
* or by registerClassLoader())
* @throws CacheException if there is a problem evicting nodes
* @throws IllegalStateException if {@link org.jboss.cache.config.Configuration#isUseRegionBasedMarshalling()} is <code>false</code>
*/
private void inactivateRegion(Fqn fqn) throws CacheException
{
if (isActivatingDeactivating(fqn))
{
throw new CacheException("Region " + fqn + " is already being activated/deactivated");
}
NodeSPI parent = null;
NodeSPI subtreeRoot = null;
boolean parentLocked = false;
boolean subtreeLocked = false;
NodeLock parentLock = null;
NodeLock subtreeLock = null;
try
{
// Record that this fqn is in status change, so can't provide state
activationChangeNodes.add(fqn);
if (!isInactive(fqn))
{
deactivate(fqn);
}
// Create a list with the Fqn in the main cache and any buddy backup trees
BuddyManager buddyManager = cache.getBuddyManager();
ArrayList<Fqn> list = new ArrayList<Fqn>();
list.add(fqn);
if (buddyManager != null)
{
Set buddies = cache.peek(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN, false, false).getChildrenNames();
if (buddies != null)
{
for (Iterator it = buddies.iterator(); it.hasNext();)
{
Fqn base = new Fqn(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN, it.next());
list.add(new Fqn(base, fqn));
}
}
}
long stateFetchTimeout = cache.getConfiguration().getLockAcquisitionTimeout() + 5000;
// Remove the subtree from the main cache and any buddy backup trees
for (Iterator<Fqn> it = list.iterator(); it.hasNext();)
{
Fqn subtree = it.next();
subtreeRoot = cache.peek(subtree, false, false);
if (subtreeRoot != null)
{
// Acquire locks
Object owner = getOwnerForLock();
subtreeLock = subtreeRoot.getLock();
subtreeLock.acquireAll(owner, stateFetchTimeout, NodeLock.LockType.WRITE);
subtreeLocked = true;
// Lock the parent, as we're about to write to it
parent = subtreeRoot.getParent();
if (parent != null)
{
parentLock = parent.getLock();
parentLock.acquire(owner, stateFetchTimeout, NodeLock.LockType.WRITE);
parentLocked = true;
}
// Remove the subtree
cache.evict(subtree, true);
//cache._evictSubtree(subtree);
// Release locks
if (parent != null)
{
log.debug("forcing release of locks in parent");
parentLock.releaseAll();
}
parentLocked = false;
log.debug("forcing release of all locks in subtree");
subtreeLock.releaseAll();
subtreeLocked = false;
}
}
}
catch (InterruptedException ie)
{
throw new CacheException("Interrupted while acquiring lock", ie);
}
finally
{
// If we didn't succeed, undo the marshalling change
// NO. Since we inactivated, we may have missed changes
//if (!success && !inactive)
// marshaller_.activate(subtreeFqn);
// If necessary, release locks
if (parentLocked)
{
log.debug("forcing release of locks in parent");
try
{
parentLock.releaseAll();
}
catch (Throwable t)
{
log.error("failed releasing locks", t);
}
}
if (subtreeLocked)
{
log.debug("forcing release of all locks in subtree");
try
{
subtreeLock.releaseAll();
}
catch (Throwable t)
{
log.error("failed releasing locks", t);
}
}
activationChangeNodes.remove(fqn);
}
}
private Object getOwnerForLock()
{
Object owner = cache.getCurrentTransaction();
return owner == null ? Thread.currentThread() : owner;
}
/**
* <p/>
* This is legacy code and should not be called directly. This is a private method for now and will be refactored out.
* You should be using {@link #activate(Fqn)} and {@link #deactivate(Fqn)}
* <p/>
*
* @param fqn fqn of the region
* @return true if the region defined by the fqn is in the process of activating/deactivating
*/
private boolean isActivatingDeactivating(Fqn fqn)
{
return activationChangeNodes.contains(fqn);
}
/**
* Returns true if the region exists
*
* @param fqn FQN of the region
* @param type type of region to search for
* @return true if the region exists
*/
public boolean hasRegion(Fqn fqn, Region.Type type)
{
Region r = regionsRegistry.get(fqn);
if (r == null) return false;
switch (type)
{
case ANY:
return true;
case EVICTION:
return r.getEvictionPolicy() != null && evictionTimerTask.isRegionRegisteredForProcessing(r);
case MARSHALLING:
return r.isActive() && r.getClassLoader() != null;
}
// should never reach here?
return false;
}
/**
* Disables unmarshalling of replication messages for the region
* rooted in the given Fqn.
*
* @param fqn
*/
public void deactivate(Fqn fqn)
{
try
{
Region region = getRegion(fqn, false);
if (region != null)
{
if (defaultInactive && region.getClassLoader() == null)
{
// This region's state will no match that of a non-existent one
// So, there is no reason to keep this region any more
// FIXME (Brian) We shouldn't do this anymore; now outside code
// can have a ref to the region!!
removeRegion(fqn);
}
else
{
//region.deactivate();
region.setActive(false);
// FIXME - we should always clean up; otherwise stale data
// is in memory!
if (cache.getConfiguration().isFetchInMemoryState())
{
inactivateRegion(fqn);
}
}
}
else if (!defaultInactive)
{
region = getRegion(fqn, true);
region.setActive(false);
// FIXME - we should always clean up; otherwise stale data
// is in memory!
if (cache.getConfiguration().isFetchInMemoryState())
{
inactivateRegion(fqn);
}
}
}
catch (RuntimeException re)
{
throw re;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
/**
* Resets the region manager's regions registry
*/
public void reset()
{
regionsRegistry.clear();
}
/**
* Returns an ordered list of all regions.
* Note that the ordered list returned is sorted according to the natural order defined in the {@link Comparable} interface, which {@link org.jboss.cache.Region} extends.
*
* @param type Type of region to return
* @return an ordered list of all regions, based on the type requested.
*/
public List<Region> getAllRegions(Region.Type type)
{
List<Region> regions;
if (type != ANY)
{
regions = new ArrayList<Region>();
// we need to loop thru the regions and only select specific regions to rtn.
for (Region r : regionsRegistry.values())
{
if ((type == EVICTION && r.getEvictionPolicy() != null && evictionTimerTask.isRegionRegisteredForProcessing(r)) ||
(type == MARSHALLING && r.isActive() && r.getClassLoader() != null))
regions.add(r);
}
}
else
{
// put all regions
regions = new ArrayList<Region>(regionsRegistry.values());
}
Collections.sort(regions);
return regions;
}
/**
* Sets if evictions are processed.
*/
public void setUsingEvictions(boolean usingEvictions)
{
this.usingEvictions = usingEvictions;
}
/**
* Sets the eviction configuration.
*/
public void setEvictionConfig(EvictionConfig evictionConfig)
{
this.evictionConfig = evictionConfig;
// JBAS-1288
// Try to establish a default region if there isn't one already
boolean needDefault = true;
List<EvictionRegionConfig> ercs = evictionConfig.getEvictionRegionConfigs();
// APPROACH 1: Scan for a default region, try to add if not there.
// This will try to add the region if it is missing but seems to break
// some unit tests that configure one or more non-default regions and
// no default region (e.g. the eviction.minttl tests). So, doing this
// seems to add a new semantic. For now comment this out and use APPROACH 2
// for (EvictionRegionConfig erc : ercs)
// {
// if (DEFAULT_REGION.equals(erc.getRegionFqn()))
// {
// needDefault = false;
// break;
// }
// }
// APPROACH 2: Only add a default region if there are no regions. This is
// contrary to the idea that there *must* be a default region, but some
// unit tests fail w/ APPROACH 1, so for now we go with this approach.
needDefault = ercs.size() == 0;
if (needDefault)
{
// This may throw ConfigurationException if there is no default
// eviction policy class
EvictionRegionConfig dflt = evictionConfig.createDefaultEvictionRegionConfig();
ercs.add(0, dflt); // put it first
// Need to pass this back into the evictionConfig so it knows
// about the new region
evictionConfig.setEvictionRegionConfigs(ercs);
}
// create regions for the regions defined in the evictionConfig.
// scan to be sure the _default_ region isn't added twice
boolean setDefault = false;
for (EvictionRegionConfig erc : ercs)
{
Fqn fqn = erc.getRegionFqn();
if (log.isTraceEnabled()) log.trace("Creating eviction region " + fqn);
if (fqn.equals(DEFAULT_REGION))
{
if (setDefault)
{
throw new ConfigurationException("A default region for evictions has already been set for this cache");
}
if (log.isTraceEnabled()) log.trace("Applying settings for " + DEFAULT_REGION + " to Fqn.ROOT");
fqn = Fqn.ROOT;
setDefault = true;
}
Region r = getRegion(fqn, true);
r.setEvictionPolicy(erc.getEvictionPolicyConfig());
}
}
/**
* Starts the eviction processing thread.
*/
public void startEvictionThread()
{
evictionTimerTask.init(evictionConfig.getWakeupIntervalSeconds());
}
/**
* Stops the eviction processing thread
*/
public void stopEvictionThread()
{
evictionTimerTask.stop();
}
/**
* Returns a string containing debug information on every region.
*/
public String dumpRegions()
{
StringBuilder sb = new StringBuilder();
for (Region r : regionsRegistry.values())
{
sb.append("\tRegion " + r);
sb.append("\n");
}
return sb.toString();
}
/**
* Returns a string containing debug information on every region.
*/
public String toString()
{
return "RegionManager " + dumpRegions();
}
public CacheSPI getCache()
{
return cache;
}
}