package org.apache.jcs.engine.control;
/*
* 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.util.ArrayList;
import java.util.HashSet;
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.access.exception.CacheException;
import org.apache.jcs.access.exception.ObjectNotFoundException;
import org.apache.jcs.auxiliary.AuxiliaryCache;
import org.apache.jcs.engine.CacheConstants;
import org.apache.jcs.engine.CacheElement;
import org.apache.jcs.engine.behavior.ICache;
import org.apache.jcs.engine.behavior.ICacheElement;
import org.apache.jcs.engine.behavior.ICacheType;
import org.apache.jcs.engine.behavior.ICompositeCacheAttributes;
import org.apache.jcs.engine.behavior.IElementAttributes;
import org.apache.jcs.engine.control.event.ElementEvent;
import org.apache.jcs.engine.control.event.ElementEventQueue;
import org.apache.jcs.engine.control.event.behavior.IElementEvent;
import org.apache.jcs.engine.control.event.behavior.IElementEventConstants;
import org.apache.jcs.engine.control.event.behavior.IElementEventHandler;
import org.apache.jcs.engine.control.event.behavior.IElementEventQueue;
import org.apache.jcs.engine.control.group.GroupId;
import org.apache.jcs.engine.memory.MemoryCache;
import org.apache.jcs.engine.memory.lru.LRUMemoryCache;
import org.apache.jcs.engine.stats.CacheStats;
import org.apache.jcs.engine.stats.StatElement;
import org.apache.jcs.engine.stats.Stats;
import org.apache.jcs.engine.stats.behavior.ICacheStats;
import org.apache.jcs.engine.stats.behavior.IStatElement;
import org.apache.jcs.engine.stats.behavior.IStats;
/**
* This is the primary hub for a single cache/region. It controls the flow of items through the
* cache. The auxiliary and memory caches are plugged in here.
* <p>
* This is the core of a JCS region. Hence, this simple class is the core of JCS.
*/
public class CompositeCache
implements ICache, Serializable
{
private static final long serialVersionUID = -2838097410378294960L;
private final static Log log = LogFactory.getLog( CompositeCache.class );
/**
* EventQueue for handling element events. 1 should be enough for all the regions. Else should
* create as needed per region.
*/
public static IElementEventQueue elementEventQ = new ElementEventQueue( "AllRegionQueue" );
// Auxiliary caches.
private AuxiliaryCache[] auxCaches = new AuxiliaryCache[0];
private boolean alive = true;
// this is in the cacheAttr, shouldn't be used, remove
final String cacheName;
/** Region Elemental Attributes, default. */
private IElementAttributes attr;
/** Cache Attributes, for hub and memory auxiliary. */
private ICompositeCacheAttributes cacheAttr;
// Statistics
private int updateCount;
private int removeCount;
/** Memory cache hit count */
private int hitCountRam;
/** Auxiliary cache hit count (number of times found in ANY auxiliary) */
private int hitCountAux;
/** Auxiliary hit counts broken down by auxiliary. */
private int[] auxHitCountByIndex;
/** Count of misses where element was not found. */
private int missCountNotFound = 0;
/** Count of misses where element was expired. */
private int missCountExpired = 0;
/**
* The cache hub can only have one memory cache. This could be made more flexible in the future,
* but they are tied closely together. More than one doesn't make much sense.
*/
private MemoryCache memCache;
/**
* Constructor for the Cache object
* <p>
* @param cacheName The name of the region
* @param cattr The cache attribute
* @param attr The default element attributes
*/
public CompositeCache( String cacheName, ICompositeCacheAttributes cattr, IElementAttributes attr )
{
this.cacheName = cacheName;
this.attr = attr;
this.cacheAttr = cattr;
createMemoryCache( cattr );
if ( log.isInfoEnabled() )
{
log.info( "Constructed cache with name [" + cacheName + "] and cache attributes " + cattr );
}
}
/**
* This sets the list of auxiliary caches for this region.
* <p>
* @param auxCaches
*/
public void setAuxCaches( AuxiliaryCache[] auxCaches )
{
this.auxCaches = auxCaches;
if ( auxCaches != null )
{
this.auxHitCountByIndex = new int[auxCaches.length];
}
}
/**
* Standard update method.
* <p>
* @param ce
* @exception IOException
*/
public synchronized void update( ICacheElement ce )
throws IOException
{
update( ce, false );
}
/**
* Standard update method.
* <p>
* @param ce
* @exception IOException
*/
public synchronized void localUpdate( ICacheElement ce )
throws IOException
{
update( ce, true );
}
/**
* Put an item into the cache. If it is localOnly, then do no notify remote or lateral
* auxiliaries.
* <p>
* @param cacheElement the ICacheElement
* @param localOnly Whether the operation should be restricted to local auxiliaries.
* @exception IOException
*/
protected synchronized void update( ICacheElement cacheElement, boolean localOnly )
throws IOException
{
// not thread safe, but just for debugging and testing.
updateCount++;
if ( cacheElement.getKey() instanceof String
&& cacheElement.getKey().toString().endsWith( CacheConstants.NAME_COMPONENT_DELIMITER ) )
{
throw new IllegalArgumentException( "key must not end with " + CacheConstants.NAME_COMPONENT_DELIMITER
+ " for a put operation" );
}
else if ( cacheElement.getKey() instanceof GroupId )
{
throw new IllegalArgumentException( "key cannot be a GroupId " + " for a put operation" );
}
if ( log.isDebugEnabled() )
{
log.debug( "Updating memory cache" );
}
memCache.update( cacheElement );
updateAuxiliaries( cacheElement, localOnly );
}
/**
* This method is responsible for updating the auxiliaries if they are present. If it is local
* only, any lateral and remote auxiliaries will not be updated.
* <p>
* Before updating an auxiliary it checks to see if the element attributes permit the operation.
* <p>
* Disk auxiliaries are only updated if the disk cache is not merely used as a swap. If the disk
* cache is merely a swap, then items will only go to disk when they overflow from memory.
* <p>
* This is called by update( cacheElement, localOnly ) after it updates the memory cache.
* <p>
* This is protected to make it testable.
* <p>
* @param cacheElement
* @param localOnly
* @throws IOException
*/
protected void updateAuxiliaries( ICacheElement cacheElement, boolean localOnly )
throws IOException
{
// UPDATE AUXILLIARY CACHES
// There are 3 types of auxiliary caches: remote, lateral, and disk
// more can be added if future auxiliary caches don't fit the model
// You could run a database cache as either a remote or a local disk.
// The types would describe the purpose.
if ( log.isDebugEnabled() )
{
if ( auxCaches.length > 0 )
{
log.debug( "Updating auxilliary caches" );
}
else
{
log.debug( "No auxilliary cache to update" );
}
}
for ( int i = 0; i < auxCaches.length; i++ )
{
ICache aux = auxCaches[i];
if ( log.isDebugEnabled() )
{
log.debug( "Auxilliary cache type: " + aux.getCacheType() );
}
if ( aux == null )
{
continue;
}
// SEND TO REMOTE STORE
if ( aux.getCacheType() == ICache.REMOTE_CACHE )
{
if ( log.isDebugEnabled() )
{
log.debug( "ce.getElementAttributes().getIsRemote() = "
+ cacheElement.getElementAttributes().getIsRemote() );
}
if ( cacheElement.getElementAttributes().getIsRemote() && !localOnly )
{
try
{
// need to make sure the group cache understands that
// the key is a group attribute on update
aux.update( cacheElement );
if ( log.isDebugEnabled() )
{
log.debug( "Updated remote store for " + cacheElement.getKey() + cacheElement );
}
}
catch ( IOException ex )
{
log.error( "Failure in updateExclude", ex );
}
}
// SEND LATERALLY
}
else if ( aux.getCacheType() == ICache.LATERAL_CACHE )
{
// lateral can't do the checking since it is dependent on the
// cache region restrictions
if ( log.isDebugEnabled() )
{
log.debug( "lateralcache in aux list: cattr " + cacheAttr.getUseLateral() );
}
if ( cacheAttr.getUseLateral() && cacheElement.getElementAttributes().getIsLateral() && !localOnly )
{
// later if we want a multicast, possibly delete abnormal
// broadcaster
// DISTRIBUTE LATERALLY
// Currently always multicast even if the value is
// unchanged,
// just to cause the cache item to move to the front.
aux.update( cacheElement );
if ( log.isDebugEnabled() )
{
log.debug( "updated lateral cache for " + cacheElement.getKey() );
}
}
}
// update disk if the usage pattern permits
else if ( aux.getCacheType() == ICache.DISK_CACHE )
{
if ( log.isDebugEnabled() )
{
log.debug( "diskcache in aux list: cattr " + cacheAttr.getUseDisk() );
}
if ( cacheAttr.getUseDisk()
&& ( cacheAttr.getDiskUsagePattern() == ICompositeCacheAttributes.DISK_USAGE_PATTERN_UPDATE )
&& cacheElement.getElementAttributes().getIsSpool() )
{
aux.update( cacheElement );
if ( log.isDebugEnabled() )
{
log.debug( "updated disk cache for " + cacheElement.getKey() );
}
}
}
}
}
/**
* Writes the specified element to any disk auxilliaries. Might want to rename this "overflow"
* incase the hub wants to do something else.
* <p>
* If JCS is not configured to use the disk as a swap, that is if the the
* CompositeCacheAttribute diskUsagePattern is not SWAP_ONLY, then the item will not be spooled.
* <p>
* @param ce The CacheElement
*/
public void spoolToDisk( ICacheElement ce )
{
// if the item is not spoolable, return
if ( !ce.getElementAttributes().getIsSpool() )
{
// there is an event defined for this.
handleElementEvent( ce, IElementEventConstants.ELEMENT_EVENT_SPOOLED_NOT_ALLOWED );
return;
}
boolean diskAvailable = false;
// SPOOL TO DISK.
for ( int i = 0; i < auxCaches.length; i++ )
{
ICache aux = auxCaches[i];
if ( aux != null && aux.getCacheType() == ICache.DISK_CACHE )
{
diskAvailable = true;
if ( cacheAttr.getDiskUsagePattern() == ICompositeCacheAttributes.DISK_USAGE_PATTERN_SWAP )
{
// write the last items to disk.2
try
{
handleElementEvent( ce, IElementEventConstants.ELEMENT_EVENT_SPOOLED_DISK_AVAILABLE );
aux.update( ce );
}
catch ( IOException ex )
{
// impossible case.
log.error( "Problem spooling item to disk cache.", ex );
throw new IllegalStateException( ex.getMessage() );
}
catch ( Exception oee )
{
// swallow
}
if ( log.isDebugEnabled() )
{
log.debug( "spoolToDisk done for: " + ce.getKey() + " on disk cache[" + i + "]" );
}
}
else
{
if ( log.isDebugEnabled() )
{
log.debug( "DiskCache avaialbe, but JCS is not configured to use the DiskCache as a swap." );
}
}
}
}
if ( !diskAvailable )
{
try
{
handleElementEvent( ce, IElementEventConstants.ELEMENT_EVENT_SPOOLED_DISK_NOT_AVAILABLE );
}
catch ( Exception e )
{
log.error( "Trouble handling the ELEMENT_EVENT_SPOOLED_DISK_NOT_AVAILABLE element event", e );
}
}
}
/**
* Gets an item from the cache.
* <p>
* @param key
* @return
* @throws IOException
* @see org.apache.jcs.engine.behavior.ICache#get(java.io.Serializable)
*/
public ICacheElement get( Serializable key )
{
return get( key, false );
}
/**
* Do not try to go remote or laterally for this get.
* <p>
* @param key
* @return ICacheElement
*/
public ICacheElement localGet( Serializable key )
{
return get( key, true );
}
/**
* Look in memory, then disk, remote, or laterally for this item. The order is dependent on the
* order in the cache.ccf file.
* <p>
* Do not try to go remote or laterally for this get if it is localOnly. Otherwise try to go
* remote or lateral if such an auxiliary is configured for this region.
* <p>
* @param key
* @param localOnly
* @return ICacheElement
*/
protected ICacheElement get( Serializable key, boolean localOnly )
{
ICacheElement element = null;
boolean found = false;
if ( log.isDebugEnabled() )
{
log.debug( "get: key = " + key + ", localOnly = " + localOnly );
}
try
{
// First look in memory cache
element = memCache.get( key );
if ( element != null )
{
// Found in memory cache
if ( isExpired( element ) )
{
if ( log.isDebugEnabled() )
{
log.debug( cacheName + " - Memory cache hit, but element expired" );
}
missCountExpired++;
remove( key );
element = null;
}
else
{
if ( log.isDebugEnabled() )
{
log.debug( cacheName + " - Memory cache hit" );
}
// Update counters
hitCountRam++;
}
found = true;
}
else
{
// Item not found in memory. If local invocation look in aux
// caches, even if not local look in disk auxiliaries
for ( int i = 0; i < auxCaches.length; i++ )
{
AuxiliaryCache aux = auxCaches[i];
if ( aux != null )
{
long cacheType = aux.getCacheType();
if ( !localOnly || cacheType == AuxiliaryCache.DISK_CACHE )
{
if ( log.isDebugEnabled() )
{
log.debug( "Attempting to get from aux [" + aux.getCacheName() + "] which is of type: "
+ cacheType );
}
try
{
element = aux.get( key );
}
catch ( IOException ex )
{
log.error( "Error getting from aux", ex );
}
}
if ( log.isDebugEnabled() )
{
log.debug( "Got CacheElement: " + element );
}
if ( element != null )
{
// Item found in one of the auxiliary caches.
if ( isExpired( element ) )
{
if ( log.isDebugEnabled() )
{
log.debug( cacheName + " - Aux cache[" + i + "] hit, but element expired." );
}
missCountExpired++;
// This will tell the remotes to remove the item
// based on the element's expiration policy. The elements attributes
// associated with the item when it created govern its behavior
// everywhere.
remove( key );
element = null;
}
else
{
if ( log.isDebugEnabled() )
{
log.debug( cacheName + " - Aux cache[" + i + "] hit" );
}
// Update counters
hitCountAux++;
auxHitCountByIndex[i]++;
// Spool the item back into memory
// only spool if the mem cache size is greater
// than 0, else the item will immediately get put
// into purgatory
if ( memCache.getCacheAttributes().getMaxObjects() > 0 )
{
memCache.update( element );
}
else
{
if ( log.isDebugEnabled() )
{
log.debug( "Skipping memory update since no items are allowed in memory" );
}
}
}
found = true;
break;
}
}
}
}
}
catch ( Exception e )
{
log.error( "Problem encountered getting element.", e );
}
if ( !found )
{
missCountNotFound++;
if ( log.isDebugEnabled() )
{
log.debug( cacheName + " - Miss" );
}
}
return element;
}
/**
* Determine if the element has exceeded its max life.
* <p>
* @param element
* @return true if the element is expired, else false.
*/
private boolean isExpired( ICacheElement element )
{
try
{
IElementAttributes attributes = element.getElementAttributes();
if ( !attributes.getIsEternal() )
{
long now = System.currentTimeMillis();
// Remove if maxLifeSeconds exceeded
long maxLifeSeconds = attributes.getMaxLifeSeconds();
long createTime = attributes.getCreateTime();
if ( maxLifeSeconds != -1 && ( now - createTime ) > ( maxLifeSeconds * 1000 ) )
{
if ( log.isDebugEnabled() )
{
log.debug( "Exceeded maxLife: " + element.getKey() );
}
return true;
}
long idleTime = attributes.getIdleTime();
long lastAccessTime = attributes.getLastAccessTime();
// Remove if maxIdleTime exceeded
// If you have a 0 size memory cache, then the last access will
// not get updated.
// you will need to set the idle time to -1.
if ( ( idleTime != -1 ) && ( now - lastAccessTime ) > ( idleTime * 1000 ) )
{
if ( log.isDebugEnabled() )
{
log.info( "Exceeded maxIdle: " + element.getKey() );
}
return true;
}
}
}
catch ( Exception e )
{
log.error( "Error determining expiration period, expiring", e );
return true;
}
return false;
}
/**
* Gets the set of keys of objects currently in the group.
* <p>
* @param group
* @return A Set of keys, or null.
*/
public Set getGroupKeys( String group )
{
HashSet allKeys = new HashSet();
allKeys.addAll( memCache.getGroupKeys( group ) );
for ( int i = 0; i < auxCaches.length; i++ )
{
AuxiliaryCache aux = auxCaches[i];
if ( aux != null )
{
try
{
allKeys.addAll( aux.getGroupKeys( group ) );
}
catch ( IOException e )
{
// ignore
}
}
}
return allKeys;
}
/**
* Removes an item from the cache.
* <p>
* @param key
* @return
* @throws IOException
* @see org.apache.jcs.engine.behavior.ICache#remove(java.io.Serializable)
*/
public boolean remove( Serializable key )
{
return remove( key, false );
}
/**
* Do not propogate removeall laterally or remotely.
* <p>
* @param key
* @return true if the item was already in the cache.
*/
public boolean localRemove( Serializable key )
{
return remove( key, true );
}
/**
* fromRemote: If a remove call was made on a cache with both, then the remote should have been
* called. If it wasn't then the remote is down. we'll assume it is down for all. If it did come
* from the remote then the cache is remotely configured and lateral removal is unncessary. If
* it came laterally then lateral removal is unnecessary. Does this assume that there is only
* one lateral and remote for the cache? Not really, the intial removal should take care of the
* problem if the source cache was similiarly configured. Otherwise the remote cache, if it had
* no laterals, would remove all the elements from remotely configured caches, but if those
* caches had some other wierd laterals that were not remotely configured, only laterally
* propagated then they would go out of synch. The same could happen for multiple remotes. If
* this looks necessary we will need to build in an identifier to specify the source of a
* removal.
* <p>
* @param key
* @param localOnly
* @return true if the item was in the cache, else false
*/
protected synchronized boolean remove( Serializable key, boolean localOnly )
{
// not thread safe, but just for debugging and testing.
removeCount++;
boolean removed = false;
try
{
removed = memCache.remove( key );
}
catch ( IOException e )
{
log.error( e );
}
// Removes from all auxiliary caches.
for ( int i = 0; i < auxCaches.length; i++ )
{
ICache aux = auxCaches[i];
if ( aux == null )
{
continue;
}
int cacheType = aux.getCacheType();
// for now let laterals call remote remove but not vice versa
if ( localOnly && ( cacheType == REMOTE_CACHE || cacheType == LATERAL_CACHE ) )
{
continue;
}
try
{
if ( log.isDebugEnabled() )
{
log.debug( "Removing " + key + " from cacheType" + cacheType );
}
boolean b = aux.remove( key );
// Don't take the remote removal into account.
if ( !removed && cacheType != REMOTE_CACHE )
{
removed = b;
}
}
catch ( IOException ex )
{
log.error( "Failure removing from aux", ex );
}
}
return removed;
}
/**
* Clears the region. This command will be sent to all auxiliaries. Some auxiliaries, such as
* the JDBC disk cache, can be configured to not honor removeAll requests.
* <p>
* @see org.apache.jcs.engine.behavior.ICache#removeAll()
*/
public void removeAll()
throws IOException
{
removeAll( false );
}
/**
* Will not pass the remove message remotely.
* <p>
* @throws IOException
*/
public void localRemoveAll()
throws IOException
{
removeAll( true );
}
/**
* Removes all cached items.
* <p>
* @param localOnly must pass in false to get remote and lateral aux's updated. This prevents
* looping.
* @throws IOException
*/
protected synchronized void removeAll( boolean localOnly )
throws IOException
{
try
{
memCache.removeAll();
if ( log.isDebugEnabled() )
{
log.debug( "Removed All keys from the memory cache." );
}
}
catch ( IOException ex )
{
log.error( "Trouble updating memory cache.", ex );
}
// Removes from all auxiliary disk caches.
for ( int i = 0; i < auxCaches.length; i++ )
{
ICache aux = auxCaches[i];
int cacheType = aux.getCacheType();
if ( aux != null && ( cacheType == ICache.DISK_CACHE || !localOnly ) )
{
try
{
if ( log.isDebugEnabled() )
{
log.debug( "Removing All keys from cacheType" + cacheType );
}
aux.removeAll();
}
catch ( IOException ex )
{
log.error( "Failure removing all from aux", ex );
}
}
}
return;
}
/**
* Flushes all cache items from memory to auxilliary caches and close the auxilliary caches.
*/
public void dispose()
{
dispose( false );
}
/**
* Invoked only by CacheManager. This method disposes of the auxiliaries one by one. For the disk cache, the items in memory
* are freed, meaning that they will be sent through the overflow chanel to disk. After the
* auxiliaries are disposed, the memory cache is dispposed.
* <p>
* @param fromRemote
*/
public synchronized void dispose( boolean fromRemote )
{
if ( log.isInfoEnabled() )
{
log.info( "In DISPOSE, [" + this.cacheName + "] fromRemote [" + fromRemote + "] \n" + this.getStats() );
}
// If already disposed, return immediately
if ( !alive )
{
return;
}
alive = false;
// Dispose of each auxilliary cache, Remote auxilliaries will be
// skipped if 'fromRemote' is true.
for ( int i = 0; i < auxCaches.length; i++ )
{
try
{
ICache aux = auxCaches[i];
// Skip this auxilliary if:
// - The auxilliary is null
// - The auxilliary is not alive
// - The auxilliary is remote and the invocation was remote
if ( aux == null || aux.getStatus() != CacheConstants.STATUS_ALIVE
|| ( fromRemote && aux.getCacheType() == REMOTE_CACHE ) )
{
if ( log.isInfoEnabled() )
{
log.info( "In DISPOSE, [" + this.cacheName + "] SKIPPING auxiliary [" + aux + "] fromRemote ["
+ fromRemote + "]" );
}
continue;
}
if ( log.isInfoEnabled() )
{
log.info( "In DISPOSE, [" + this.cacheName + "] auxiliary [" + aux + "]" );
}
// IT USED TO BE THE CASE THAT (If the auxilliary is not a lateral, or the cache
// attributes
// have 'getUseLateral' set, all the elements currently in
// memory are written to the lateral before disposing)
// I changed this. It was excessive. Only the disk cache needs the items, since only
// the disk cache
// is in a situation to not get items on a put.
if ( aux.getCacheType() == ICacheType.DISK_CACHE )
{
int numToFree = memCache.getSize();
memCache.freeElements( numToFree );
if ( log.isInfoEnabled() )
{
log.info( "In DISPOSE, [" + this.cacheName + "] put " + numToFree + " into auxiliary " + aux );
}
}
// Dispose of the auxiliary
aux.dispose();
}
catch ( IOException ex )
{
log.error( "Failure disposing of aux.", ex );
}
}
if ( log.isInfoEnabled() )
{
log.info( "In DISPOSE, [" + this.cacheName + "] disposing of memory cache." );
}
try
{
memCache.dispose();
}
catch ( IOException ex )
{
log.error( "Failure disposing of memCache", ex );
}
}
/**
* Calling save cause the entire contents of the memory cache to be flushed to all auxiliaries.
* Though this put is extremely fast, this could bog the cache and should be avoided. The
* dispose method should call a version of this. Good for testing.
*/
public void save()
{
if ( !alive )
{
return;
}
synchronized ( this )
{
if ( !alive )
{
return;
}
alive = false;
for ( int i = 0; i < auxCaches.length; i++ )
{
try
{
ICache aux = auxCaches[i];
if ( aux.getStatus() == CacheConstants.STATUS_ALIVE )
{
Iterator itr = memCache.getIterator();
while ( itr.hasNext() )
{
Map.Entry entry = (Map.Entry) itr.next();
ICacheElement ce = (ICacheElement) entry.getValue();
aux.update( ce );
}
}
}
catch ( IOException ex )
{
log.error( "Failure saving aux caches.", ex );
}
}
}
if ( log.isDebugEnabled() )
{
log.debug( "Called save for [" + cacheName + "]" );
}
}
/**
* Gets the size attribute of the Cache object. This return the number of elements, not the byte
* size.
* <p>
* @return The size value
*/
public int getSize()
{
return memCache.getSize();
}
/**
* Gets the cacheType attribute of the Cache object.
* <p>
* @return The cacheType value
*/
public int getCacheType()
{
return CACHE_HUB;
}
/**
* Gets the status attribute of the Cache object.
* <p>
* @return The status value
*/
public int getStatus()
{
return alive ? CacheConstants.STATUS_ALIVE : CacheConstants.STATUS_DISPOSED;
}
/**
* Gets stats for debugging.
* <p>
* @return String
*/
public String getStats()
{
return getStatistics().toString();
}
/**
* This returns data gathered for this region and all the auxiliaries it currently uses.
* <p>
* @return Statistics and Info on the Region.
*/
public ICacheStats getStatistics()
{
ICacheStats stats = new CacheStats();
stats.setRegionName( this.getCacheName() );
// store the composite cache stats first
IStatElement[] elems = new StatElement[2];
elems[0] = new StatElement();
elems[0].setName( "HitCountRam" );
elems[0].setData( "" + getHitCountRam() );
elems[1] = new StatElement();
elems[1].setName( "HitCountAux" );
elems[1].setData( "" + getHitCountAux() );
// store these local stats
stats.setStatElements( elems );
// memory + aux, memory is not considered an auxiliary internally
int total = auxCaches.length + 1;
IStats[] auxStats = new Stats[total];
auxStats[0] = getMemoryCache().getStatistics();
for ( int i = 0; i < auxCaches.length; i++ )
{
AuxiliaryCache aux = auxCaches[i];
auxStats[i + 1] = aux.getStatistics();
}
// sore the auxiliary stats
stats.setAuxiliaryCacheStats( auxStats );
return stats;
}
/**
* Gets the cacheName attribute of the Cache object. This is also known as the region name.
* <p>
* @return The cacheName value
*/
public String getCacheName()
{
return cacheName;
}
/**
* Gets the default element attribute of the Cache object This returna a copy. It does not
* return a reference to the attributes.
* <p>
* @return The attributes value
*/
public IElementAttributes getElementAttributes()
{
if ( attr != null )
{
return attr.copy();
}
return null;
}
/**
* Sets the default element attribute of the Cache object.
* <p>
* @param attr
*/
public void setElementAttributes( IElementAttributes attr )
{
this.attr = attr;
}
/**
* Gets the ICompositeCacheAttributes attribute of the Cache object.
* <p>
* @return The ICompositeCacheAttributes value
*/
public ICompositeCacheAttributes getCacheAttributes()
{
return this.cacheAttr;
}
/**
* Sets the ICompositeCacheAttributes attribute of the Cache object.
* <p>
* @param cattr The new ICompositeCacheAttributes value
*/
public void setCacheAttributes( ICompositeCacheAttributes cattr )
{
this.cacheAttr = cattr;
// need a better way to do this, what if it is in error
this.memCache.initialize( this );
}
/**
* Gets the elementAttributes attribute of the Cache object.
* <p>
* @param key
* @return The elementAttributes value
* @exception CacheException
* @exception IOException
*/
public IElementAttributes getElementAttributes( Serializable key )
throws CacheException, IOException
{
CacheElement ce = (CacheElement) get( key );
if ( ce == null )
{
throw new ObjectNotFoundException( "key " + key + " is not found" );
}
return ce.getElementAttributes();
}
/**
* Create the MemoryCache based on the config parameters. TODO: consider making this an
* auxiliary, despite its close tie to the CacheHub. TODO: might want to create a memory cache
* config file separate from that of the hub -- ICompositeCacheAttributes
* <p>
* @param cattr
*/
private void createMemoryCache( ICompositeCacheAttributes cattr )
{
if ( memCache == null )
{
try
{
Class c = Class.forName( cattr.getMemoryCacheName() );
memCache = (MemoryCache) c.newInstance();
memCache.initialize( this );
}
catch ( Exception e )
{
log.warn( "Failed to init mem cache, using: LRUMemoryCache", e );
this.memCache = new LRUMemoryCache();
this.memCache.initialize( this );
}
}
else
{
log.warn( "Refusing to create memory cache -- already exists." );
}
}
/**
* Access to the memory cache for instrumentation.
* <p>
* @return the MemoryCache implementation
*/
public MemoryCache getMemoryCache()
{
return memCache;
}
/**
* Number of times a requested item was found in the memory cache.
* <p>
* @return number of hits in memory
*/
public int getHitCountRam()
{
return hitCountRam;
}
/**
* Number of times a requested item was found in and auxiliary cache.
* @return number of auxiliary hits.
*/
public int getHitCountAux()
{
return hitCountAux;
}
/**
* Number of times a requested element was not found.
* @return number of misses.
*/
public int getMissCountNotFound()
{
return missCountNotFound;
}
/**
* Number of times a requested element was found but was expired.
* @return number of found but expired gets.
*/
public int getMissCountExpired()
{
return missCountExpired;
}
/**
* If there are event handlers for the item, then create an event and queue it up.
* <p>
* This does not call handle directly; instead the handler and the event are put into a queue.
* This prevents the event handling from blocking normal cache operations.
* @param ce
* @param eventType
*/
private void handleElementEvent( ICacheElement ce, int eventType )
{
// handle event, might move to a new method
ArrayList eventHandlers = ce.getElementAttributes().getElementEventHandlers();
if ( eventHandlers != null )
{
if ( log.isDebugEnabled() )
{
log.debug( "Element Handlers are registered. Create event type " + eventType );
}
IElementEvent event = new ElementEvent( ce, eventType );
Iterator hIt = eventHandlers.iterator();
while ( hIt.hasNext() )
{
IElementEventHandler hand = (IElementEventHandler) hIt.next();
try
{
addElementEvent( hand, event );
}
catch ( Exception e )
{
log.error( "Trouble adding element event to queue", e );
}
}
}
}
/**
* Adds an ElementEvent to be handled to the queue.
* @param hand The IElementEventHandler
* @param event The IElementEventHandler IElementEvent event
* @exception IOException Description of the Exception
*/
public void addElementEvent( IElementEventHandler hand, IElementEvent event )
throws IOException
{
if ( log.isDebugEnabled() )
{
log.debug( "Adding event to Element Event Queue" );
}
elementEventQ.addElementEvent( hand, event );
}
/**
* @param updateCount The updateCount to set.
*/
public void setUpdateCount( int updateCount )
{
this.updateCount = updateCount;
}
/**
* @return Returns the updateCount.
*/
public int getUpdateCount()
{
return updateCount;
}
/**
* @param removeCount The removeCount to set.
*/
public void setRemoveCount( int removeCount )
{
this.removeCount = removeCount;
}
/**
* @return Returns the removeCount.
*/
public int getRemoveCount()
{
return removeCount;
}
/**
* This returns the stats.
* <p>
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString()
{
return getStats();
}
}