/* Open Source Java Caching Service
* Copyright (C) 2002 Frank Karlstr�m
* This library 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 library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* The author can be contacted by email: fjankk@users.sourceforge.net
*/
package org.fjank.jcache;
import java.lang.ref.ReferenceQueue;
import java.util.Iterator;
import javax.util.jcache.Attributes;
import javax.util.jcache.CacheAttributes;
import javax.util.jcache.CacheException;
import javax.util.jcache.CacheNotAvailableException;
import javax.util.jcache.NullObjectException;
/**
* A Runnable class for Sweeping the cache for expired objects. Either they are
* serialized to disk, or they are removed from the cache depending on the
* attributes of the object.
*
* @author Frank Karlstr�m
*/
public class CacheSweeper implements Runnable {
/** the singleton sweeper */
private static CacheSweeper _singleton;
/** a boolean indication wether this sweeper is active or not. */
//2004-09-FB
private boolean active = false;
/**
* Creates new CacheSweeper
*/
private CacheSweeper() {
}
/**
* sweeps the cache and removes invalid objects, waits for cleanInterval
* seconds, then sweeps again etc.
*/
public void run() {
CacheImpl cache = CacheImpl.getCache(true);
CacheAttributes attribs = cache.getAttributes();
while (active) {
try {
Thread.sleep(attribs.getCleanInterval() * 1000);
} catch (InterruptedException e) {
//normal
}
sweepCache();
}
}
/**
* checks the size of the objects in the cache, and if the memory limit
* is nearing its capacity, somoe objects are saved to disk. The
* algorithm for choosing these is in the method writeToDisk(). Then the
* ReferenceQueue is checked, and all objects in this queue have been
* swept by the GC. if they have expired, they are deleted from the cache.
* If not, they are retained to the next cachesweep.
*/
private void sweepCache() {
CacheImpl cache = null;
try {
cache = CacheImpl.getCache(true);
int maxSize = cache.getAttributes().getMemoryCacheSize();
long maxBytes = maxSize * 1024 * 1024;
if (getMemoryCacheSize() >= maxBytes) {
writeToDisk();
}
/**sweeps the entire cache, and de-references the expired objects.
* This will add them to the appropiate ReferenceQueue
* when their referenceCount is zero. If no one uses the objects,
* this will happen immediantely.
* We also attempt to stimulate the gc to run. (Not neccessary perhaps?)
*
*/
CacheRegion reg = cache.getRegion();
sweepGroup(reg);
Iterator iter = cache.userRegionNames();
while(iter.hasNext()) {
Object key = iter.next();
CacheRegion userReg = cache.getRegion(key);
sweepGroup(userReg);
}
for(int i=0; i<3; i++) {
System.runFinalization();
System.gc();
try {
Thread.sleep(500);
} catch (InterruptedException e1) {;}
}
/**now its up to the referencequeue to remove the
* appropiate obects.
*/
CacheObject cacheObj = null;
ReferenceQueue q = cache.getReferenceQueue();
while ((cacheObj = (CacheObject) q.poll()) != null) {
cacheObj.resetRefCount();
tryRemoval(cacheObj);
}
} catch (CacheException e) {
e.printStackTrace();
stopSweeper();
} catch (java.lang.IncompatibleClassChangeError e) {
//the cache has probably been reloaded/redeployed.
//restart ourselves.
if (cache != null) {
cache.close();
try {
cache.open();
} catch (CacheNotAvailableException ee) {
stopSweeper();
}
}
}
}
/**
* Will sweep a group in search for an object which has expired.
* any groups encountered will use this method as a recursive method
* to further search for expired objects.
* @todo there's no failsafe for groups containing themselves.
* @param group the group to sweep.
* @throws NullObjectException if a nullobject is encountered.
*/
private void sweepGroup(CacheGroup group) throws NullObjectException {
Iterator iter = group.weakReferenceObjects.keySet().iterator();
while(iter.hasNext()) {
Object key = iter.next();
Object obj = group.weakReferenceObjects.get(key);
if(obj instanceof CacheObject) {
if(hasExpired((CacheObject) obj)) {
group.removeObjectReference(key);
}
}else if(obj instanceof CacheGroup) {
sweepGroup((CacheGroup) obj);
}else {
System.out.println("An unknown object ("+obj.getClass().getName()+") was discovered in the cache.");
break;
}
}
}
/**
* the actual remove routine. if the object has been alive longer than the
* timeToLive , its invalidated.
* @todo Some of the code here is duplicated in tryRemoveHardReference.
* @todo return value is never used.
* @param cacheObj the CacheObject to try to remove.
* @return an boolean indication wether the object was removed or not.
*/
private boolean tryRemoval(final CacheObject cacheObj) throws NullObjectException {
if (hasExpired(cacheObj)) {
cacheObj.invalidate();
return true;
}
return false;
}
private boolean hasExpired(CacheObject obj) throws NullObjectException {
Attributes attributes = obj.getAttributes();
if(attributes==null) throw new NullObjectException("A null attributes was detected.");
/*-1 is the default for attributes, and the default is
* non invalidation, so if the ttl is -1, the objects remains in the cache.
*/
if(attributes.getTimeToLive()==-1) {
return false;
}
if(attributes.getLoader()!=null) return false;
long now = System.currentTimeMillis();
long timealive = (now - attributes.getCreateTime()) / 1000;
//only ttl is support for now. the rest of the params can be implemented later.
if (timealive >= attributes.getTimeToLive()) {
return true;
}
return false;
}
/**
* Finds all objects wich are not in use, and determines wich one of them
* is the best to flush to disk. LFU is a caching strategy that stands for
* "least frequently used". In this strategy, elements are evicted based
* when they were added, when they were last used, and how many times they
* have been used. Other strategies may be implemented later. Gets the
* diskFile, fins an available position in the file, converts the
* CacheObject to a CacheObjectDisk, and writes it to the disk file.
*
*@todo NOT IMPLEMENTED.
*/
private void writeToDisk() {
}
/**
* Gets the current size of all objects in the cache. Return the size of
* the actuall object, not regarding the size of the attributes, or the
* wrapper objects.
*
* @return long the current size of all objects in the cache.
*
*@todo NOT IMPLEMENTED.
*/
private long getMemoryCacheSize() {
return 0;
}
/**
* activates this sweeper.
*/
synchronized void startSweeper() {
//2004/09-FB
if (!_singleton.active){
active=true;
CacheThreadFactory fact = CacheThreadFactory.getInstance();
fact.setName("Fjanks FKache - CacheSweeper");
fact.setDaemon(true);
fact.newThread(this).start();
}
}
/**
* deactivates this sweeper.
*/
//2004/09-FB
synchronized void stopSweeper() {
active = false;
}
/**
* create this sweeper instance.
*/
public static synchronized CacheSweeper getInstance() {
if (_singleton == null) {
_singleton = new CacheSweeper();
//2004/09-FB
//_singleton.startSweeper();
}
return _singleton;
}
//2004/09-FB
/**
* remove this sweeper instance.
*/
static synchronized void removeInstance() {
if (_singleton != null) {
if (_singleton.active){
_singleton.stopSweeper();
}
_singleton = null;
}
}
}