/*******************************************************************************
* Copyright 2010 Cees De Groot, Alex Boisvert, Jan Kotek
*
* Licensed 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.
******************************************************************************/
package de.mxro.thrd.jdbm2V22.recman;
import java.io.IOException;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import de.mxro.thrd.jdbm2V22.RecordManager;
import de.mxro.thrd.jdbm2V22.Serializer;
import de.mxro.thrd.jdbm2V22.helper.LongHashMap;
import de.mxro.thrd.jdbm2V22.helper.RecordManagerImpl;
/**
* A RecordManager wrapping and caching another RecordManager.
*
* @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
* @author <a href="cg@cdegroot.com">Cees de Groot</a>
* @version $Id: CacheRecordManager.java,v 1.9 2005/06/25 23:12:32 doomdark Exp $
*/
public class CacheRecordManager
extends RecordManagerImpl
{
/**
* Wrapped RecordManager
*/
protected RecordManager _recman;
/** Cached object hashtable */
protected LongHashMap<CacheEntry> _hash;
/** If Soft Cache is enabled, this contains softly referenced clean entries.
* If entry became dirty, it is moved to _hash with limited size.
* This map is accessed from SoftCache Disposer thread, so all access must be
* synchronized
*/
protected LongHashMap<SoftCacheEntry> _softHash;
/**
* Reference queue used to collect Soft Cache entries
*/
protected ReferenceQueue<SoftCacheEntry> _refQueue;
/**
* Maximum number of objects in the cache.
*/
protected int _max;
/**
* True if enable second level soft cache
*/
protected boolean _softCache;
/**
* Thread in which Soft Cache references are disposed
*/
protected Thread _softRefThread;
protected static int threadCounter = 0;
/**
* Beginning of linked-list of cache elements. First entry is element
* which has been used least recently.
*/
protected CacheEntry _first;
/**
* End of linked-list of cache elements. Last entry is element
* which has been used most recently.
*/
protected CacheEntry _last;
/**
* Construct a CacheRecordManager wrapping another RecordManager and
* using a given cache policy.
*
* @param recman Wrapped RecordManager
* @param cache Cache policy
*/
public CacheRecordManager( RecordManager recman, int maxRecords, boolean softCache)
{
if ( recman == null ) {
throw new IllegalArgumentException( "Argument 'recman' is null" );
}
_hash = new LongHashMap<CacheEntry>(maxRecords);
_recman = recman;
_max = maxRecords;
_softCache = softCache;
if(softCache){
_softHash = new LongHashMap<SoftCacheEntry>();
_refQueue = new ReferenceQueue<SoftCacheEntry>();
_softRefThread = new Thread(
new SoftRunnable(this, _refQueue),
"JDBM Soft Cache Disposer "+(threadCounter++));
_softRefThread.setDaemon(true);
_softRefThread.start();
}
}
/**
* Get the underlying Record Manager.
*
* @return underlying RecordManager or null if CacheRecordManager has
* been closed.
*/
public RecordManager getRecordManager()
{
return _recman;
}
public synchronized <A> long insert( A obj, Serializer<A> serializer )
throws IOException
{
checkIfClosed();
long recid = _recman.insert( obj, serializer );
//DONT use cache for inserts, it usually hurts performance on batch inserts
// if(_softCache) synchronized(_softHash) {
// _softHash.put(recid, new SoftCacheEntry(recid, obj, serializer,_refQueue));
// }else {
// cachePut( recid , obj, serializer, false );
// }
return recid;
}
public synchronized <A> A fetch( long recid, Serializer<A> serializer, boolean disableCache ) throws IOException{
if(disableCache)
return _recman.fetch(recid, serializer,disableCache);
else
return fetch(recid,serializer);
}
public synchronized void delete( long recid )
throws IOException
{
checkIfClosed();
_recman.delete( recid );
CacheEntry entry = _hash.get(recid);
if (entry != null) {
removeEntry(entry);
_hash.remove(entry._recid);
}
if(_softCache) synchronized(_softHash) {
SoftCacheEntry e = _softHash.remove(recid);
if(e!=null){
e.clear();
}
}
}
public synchronized <A> void update( long recid, A obj,
Serializer<A> serializer )
throws IOException
{
checkIfClosed();
if(_softCache) synchronized(_softHash) {
//soft cache can not contain dirty objects
SoftCacheEntry e = _softHash.remove(recid);
if(e != null){
e.clear();
}
}
CacheEntry entry = cacheGet(recid);
if ( entry != null ) {
// reuse existing cache entry
entry._obj = obj;
entry._serializer = serializer;
entry._isDirty = true;
} else {
cachePut( recid, obj, serializer, true );
}
}
public synchronized <A> A fetch( long recid, Serializer<A> serializer )
throws IOException
{
checkIfClosed();
if(_softCache) synchronized(_softHash){
SoftCacheEntry e = _softHash.get(recid);
if(e!=null){
Object a = e.get();
if(a!=null){
return (A) a;
}
}
}
CacheEntry entry = (CacheEntry) cacheGet( recid );
if ( entry == null ) {
A value = _recman.fetch( recid, serializer );
if(!_softCache)
cachePut(recid,value, serializer,false);
else{ //put record into soft cache
synchronized(_softHash){
_softHash.put(recid,new SoftCacheEntry(recid, value, _refQueue));
}
}
return value;
}else{
return (A) entry._obj;
}
}
public synchronized void close()
throws IOException
{
checkIfClosed();
updateCacheEntries();
_recman.close();
_recman = null;
_hash = null;
_softHash = null;
if(_softCache)
_softRefThread.interrupt();
}
public synchronized void commit()
throws IOException
{
checkIfClosed();
updateCacheEntries();
_recman.commit();
}
public synchronized void rollback()
throws IOException
{
checkIfClosed();
_recman.rollback();
// discard all cache entries since we don't know which entries
// where part of the transaction
_hash.clear();
if(_softCache) synchronized(_softHash) {
Iterator<SoftCacheEntry> iter = _softHash.valuesIterator();
while(iter.hasNext()){
SoftCacheEntry e = iter.next();
e.clear();
}
_softHash.clear();
}
_first = null;
_last = null;
}
public synchronized long getNamedObject( String name )
throws IOException
{
checkIfClosed();
return _recman.getNamedObject( name );
}
public synchronized void setNamedObject( String name, long recid )
throws IOException
{
checkIfClosed();
_recman.setNamedObject( name, recid );
}
/**
* Check if RecordManager has been closed. If so, throw an
* IllegalStateException
*/
protected void checkIfClosed()
throws IllegalStateException
{
if ( _recman == null ) {
throw new IllegalStateException( "RecordManager has been closed" );
}
}
/**
* Update all dirty cache objects to the underlying RecordManager.
*/
protected void updateCacheEntries()
throws IOException
{
Iterator<CacheEntry> iter = _hash.valuesIterator();
while(iter.hasNext()){
CacheEntry entry = iter.next();
if ( entry._isDirty ) {
_recman.update( entry._recid, entry._obj, entry._serializer );
entry._isDirty = false;
}
}
}
/**
* Obtain an object in the cache
*/
protected CacheEntry cacheGet(long key) {
CacheEntry entry = _hash.get(key);
if (entry != null)
touchEntry(entry);
return entry;
}
/**
* Place an object in the cache.
* @throws IOException
*/
protected void cachePut(long recid, Object value, Serializer serializer, boolean dirty) throws IOException {
CacheEntry entry = _hash.get(recid);
if (entry != null) {
entry._obj = value;
entry._serializer = serializer;
touchEntry(entry);
} else {
if (_hash.size() == _max) {
// purge and recycle entry
entry = purgeEntry();
entry._recid = recid;
entry. _obj = value;
entry._isDirty = dirty;
entry._serializer = serializer;
} else {
entry = new CacheEntry(recid, value, serializer,dirty);
}
addEntry(entry);
_hash.put(entry._recid, entry);
}
}
/**
* Add a CacheEntry. Entry goes at the end of the list.
*/
protected void addEntry(CacheEntry entry) {
if (_first == null) {
_first = entry;
_last = entry;
} else {
_last._next = entry;
entry._previous = _last;
_last = entry;
}
}
/**
* Remove a CacheEntry from linked list
*/
protected void removeEntry(CacheEntry entry) {
if (entry == _first) {
_first = entry._next;
}
if (_last == entry) {
_last = entry._previous;
}
CacheEntry previous = entry._previous;
CacheEntry next = entry._next;
if (previous != null) {
previous._next = next;
}
if (next != null) {
next._previous = previous;
}
entry._previous = null;
entry._next = null;
}
/**
* Place entry at the end of linked list -- Most Recently Used
*/
protected void touchEntry(CacheEntry entry) {
if (_last == entry) {
return;
}
removeEntry(entry);
addEntry(entry);
}
/**
* Purge least recently used object from the cache
*
* @return recyclable CacheEntry
*/
protected CacheEntry purgeEntry() throws IOException {
CacheEntry entry = _first;
if(entry == null)
return new CacheEntry(-1,null,null,false);
if(entry._isDirty)
_recman.update( entry._recid, entry._obj, entry._serializer );
removeEntry(entry);
_hash.remove(entry._recid);
entry._obj = null;
entry._serializer = null;
entry._isDirty = false;
return entry;
}
@SuppressWarnings("unchecked")
protected static final class CacheEntry
{
protected long _recid;
protected Object _obj;
protected Serializer _serializer;
protected boolean _isDirty;
protected CacheEntry _previous;
protected CacheEntry _next;
CacheEntry( long recid, Object obj, Serializer serializer, boolean isDirty )
{
_recid = recid;
_obj = obj;
_serializer = serializer;
_isDirty = isDirty;
}
}
@SuppressWarnings("unchecked")
protected static final class SoftCacheEntry extends SoftReference
{
protected long _recid;
SoftCacheEntry( long recid, Object obj, ReferenceQueue queue)
{
super(obj,queue);
_recid = recid;
}
}
/**
* Runs in separate thread and cleans SoftCache.
* Runnable auto exists when CacheRecordManager is GCed
*
* @author Jan Kotek
*
*/
protected static final class SoftRunnable implements Runnable{
private ReferenceQueue<SoftCacheEntry> entryQueue;
private WeakReference<CacheRecordManager> recman2;
public SoftRunnable(CacheRecordManager recman,
ReferenceQueue<SoftCacheEntry> entryQueue) {
this.recman2 = new WeakReference<CacheRecordManager>(recman);
this.entryQueue = entryQueue;
}
public void run() {
while(true)try{
//collect next item from cache,
//limit 10000 ms is to keep periodically checking if recman was GCed
SoftCacheEntry e = (SoftCacheEntry) entryQueue.remove(10000);
//check if recman was GCed, cancel in that case
CacheRecordManager recman = recman2.get();
if(recman == null)
return;
if(e!=null){
synchronized(recman._softHash){
while(e!=null){
recman._softHash.remove(e._recid);
e = (SoftCacheEntry) entryQueue.poll();
}
}
}
}catch (InterruptedException e){
return;
}catch (Throwable e){
//this thread must keep spinning,
//otherwise SoftCacheEntries would not be disposed
e.printStackTrace();
}
}
}
public void clearCache() throws IOException {
// discard all cache entries since we don't know which entries
// where part of the transaction
while(_hash.size()>0)
purgeEntry();
if(_softCache) synchronized(_softHash) {
Iterator<SoftCacheEntry> iter = _softHash.valuesIterator();
while(iter.hasNext()){
SoftCacheEntry e = iter.next();
e.clear();
}
_softHash.clear();
}
_first = null;
_last = null;
}
public void defrag() throws IOException {
commit();
_recman.defrag();
}
}