blob: 5728683d6c2e16fc2222a777b577355389b28165 [file] [log] [blame]
/**
* JDBM LICENSE v1.00
*
* Redistribution and use of this software and associated documentation
* ("Software"), with or without modification, are permitted provided
* that the following conditions are met:
*
* 1. Redistributions of source code must retain copyright
* statements and notices. Redistributions must also contain a
* copy of this document.
*
* 2. Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* 3. The name "JDBM" must not be used to endorse or promote
* products derived from this Software without prior written
* permission of Cees de Groot. For written permission,
* please contact cg@cdegroot.com.
*
* 4. Products derived from this Software may not be called "JDBM"
* nor may "JDBM" appear in their names without prior written
* permission of Cees de Groot.
*
* 5. Due credit should be given to the JDBM Project
* (http://jdbm.sourceforge.net/).
*
* THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Copyright 2000 (C) Cees de Groot. All Rights Reserved.
* Copyright 2000-2001 (C) Alex Boisvert. All Rights Reserved.
* Contributions are Copyright (C) 2000 by their associated contributors.
*
* $Id: CacheRecordManager.java,v 1.9 2005/06/25 23:12:32 doomdark Exp $
*/
package jdbm.recman;
import jdbm.RecordManager;
import jdbm.helper.CacheEvictionException;
import jdbm.helper.CachePolicy;
import jdbm.helper.CachePolicyListener;
import jdbm.helper.DefaultSerializer;
import jdbm.helper.Serializer;
import jdbm.helper.WrappedRuntimeException;
import java.io.IOException;
import java.util.Enumeration;
import org.apache.directory.server.i18n.I18n;
/**
* 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>
*/
public class CacheRecordManager implements RecordManager
{
/** Wrapped RecordManager */
protected RecordManager recordManager;
/** Cache for underlying RecordManager */
protected CachePolicy<Long, CacheEntry> cache;
/**
* Construct a CacheRecordManager wrapping another RecordManager and
* using a given cache policy.
*
* @param recordManager Wrapped RecordManager
* @param cache Cache policy
*/
public CacheRecordManager( RecordManager recordManager, CachePolicy<Long,CacheEntry> cache )
{
if ( recordManager == null )
{
throw new IllegalArgumentException( I18n.err( I18n.ERR_517 ) );
}
if ( cache == null )
{
throw new IllegalArgumentException( I18n.err( I18n.ERR_542 ) );
}
this.recordManager = recordManager;
this.cache = cache;
this.cache.addListener( new CacheListener() );
}
/**
* Get the underlying Record Manager.
*
* @return underlying RecordManager or null if CacheRecordManager has
* been closed.
*/
public RecordManager getRecordManager()
{
return recordManager;
}
/**
* Get the underlying cache policy
*
* @return underlying CachePolicy or null if CacheRecordManager has
* been closed.
*/
public CachePolicy<Long,CacheEntry> getCachePolicy()
{
return cache;
}
/**
* Inserts a new record using a custom serializer.
*
* @param obj the object for the new record.
* @return the rowid for the new record.
* @throws IOException when one of the underlying I/O operations fails.
*/
public long insert( Object obj ) throws IOException
{
return insert( obj, DefaultSerializer.INSTANCE );
}
/**
* Inserts a new record using a custom serializer.
*
* @param obj the object for the new record.
* @param serializer a custom serializer
* @return the rowid for the new record.
* @throws IOException when one of the underlying I/O operations fails.
*/
public synchronized long insert( Object obj, Serializer serializer ) throws IOException
{
checkIfClosed();
long recid = recordManager.insert( obj, serializer );
try
{
cache.put( recid, new CacheEntry( recid, obj, serializer, false ) );
}
catch ( CacheEvictionException except )
{
throw new WrappedRuntimeException( except );
}
return recid;
}
/**
* Deletes a record.
*
* @param recid the rowid for the record that should be deleted.
* @throws IOException when one of the underlying I/O operations fails.
*/
public synchronized void delete( long recid ) throws IOException
{
checkIfClosed();
// Remove the entry from the underlying storage
recordManager.delete( recid );
// And now update the cache
cache.remove( recid );
}
/**
* Updates a record using standard Java serialization.
*
* @param recid the recid for the record that is to be updated.
* @param obj the new object for the record.
* @throws IOException when one of the underlying I/O operations fails.
*/
public void update( long recid, Object obj ) throws IOException
{
update( recid, obj, DefaultSerializer.INSTANCE );
}
/**
* Updates a record using a custom serializer.
*
* @param recid the recid for the record that is to be updated.
* @param obj the new object for the record.
* @param serializer a custom serializer
* @throws IOException when one of the underlying I/O operations fails.
*/
public synchronized void update( long recid, Object obj, Serializer serializer ) throws IOException
{
checkIfClosed();
try {
CacheEntry entry = cache.get( recid );
if ( entry != null )
{
// reuse existing cache entry
entry.obj = obj;
entry.serializer = serializer;
entry.isDirty = true;
}
else
{
cache.put( recid, new CacheEntry( recid, obj, serializer, true ) );
}
}
catch ( CacheEvictionException except )
{
throw new IOException( except.getLocalizedMessage() );
}
}
/**
* Fetches a record using standard Java serialization.
*
* @param recid the recid for the record that must be fetched.
* @return the object contained in the record.
* @throws IOException when one of the underlying I/O operations fails.
*/
public Object fetch( long recid ) throws IOException
{
return fetch( recid, DefaultSerializer.INSTANCE );
}
/**
* Fetches a record using a custom serializer.
*
* @param recid the recid for the record that must be fetched.
* @param serializer a custom serializer
* @return the object contained in the record.
* @throws IOException when one of the underlying I/O operations fails.
*/
public synchronized Object fetch( long recid, Serializer serializer ) throws IOException
{
checkIfClosed();
CacheEntry entry = cache.get( recid );
if ( entry == null )
{
entry = new CacheEntry( recid, null, serializer, false );
entry.obj = recordManager.fetch( recid, serializer );
try
{
cache.put( recid, entry );
}
catch ( CacheEvictionException except )
{
throw new WrappedRuntimeException( except );
}
}
if ( entry.obj instanceof byte[] )
{
byte[] copy = new byte[ ( ( byte[] ) entry.obj ).length ];
System.arraycopy( entry.obj, 0, copy, 0, ( ( byte[] ) entry.obj ).length );
return copy;
}
return entry.obj;
}
/**
* Closes the record manager.
*
* @throws IOException when one of the underlying I/O operations fails.
*/
public synchronized void close() throws IOException
{
checkIfClosed();
updateCacheEntries();
recordManager.close();
recordManager = null;
cache = null;
}
/**
* Returns the number of slots available for "root" rowids. These slots
* can be used to store special rowids, like rowids that point to
* other rowids. Root rowids are useful for bootstrapping access to
* a set of data.
*/
public synchronized int getRootCount()
{
checkIfClosed();
return recordManager.getRootCount();
}
/**
* Returns the indicated root rowid.
*
* @see #getRootCount
*/
public synchronized long getRoot( int id ) throws IOException
{
checkIfClosed();
return recordManager.getRoot( id );
}
/**
* Sets the indicated root rowid.
*
* @see #getRootCount
*/
public synchronized void setRoot( int id, long rowid ) throws IOException
{
checkIfClosed();
recordManager.setRoot( id, rowid );
}
/**
* Commit (make persistent) all changes since beginning of transaction.
*/
public synchronized void commit() throws IOException
{
checkIfClosed();
updateCacheEntries();
recordManager.commit();
}
/**
* Rollback (cancel) all changes since beginning of transaction.
*/
public synchronized void rollback() throws IOException
{
checkIfClosed();
recordManager.rollback();
// discard all cache entries since we don't know which entries
// where part of the transaction
cache.removeAll();
}
/**
* Obtain the record id of a named object. Returns 0 if named object
* doesn't exist.
*/
public synchronized long getNamedObject( String name ) throws IOException
{
checkIfClosed();
return recordManager.getNamedObject( name );
}
/**
* Set the record id of a named object.
*/
public synchronized void setNamedObject( String name, long recid ) throws IOException
{
checkIfClosed();
recordManager.setNamedObject( name, recid );
}
/**
* Check if RecordManager has been closed. If so, throw an IllegalStateException
*/
private void checkIfClosed() throws IllegalStateException
{
if ( recordManager == null )
{
throw new IllegalStateException( I18n.err( I18n.ERR_538 ) );
}
}
/**
* Update all dirty cache objects to the underlying RecordManager.
*/
protected void updateCacheEntries() throws IOException
{
Enumeration<CacheEntry> enume = cache.elements();
while ( enume.hasMoreElements() )
{
CacheEntry entry = enume.nextElement();
if ( entry.isDirty )
{
recordManager.update( entry.recid, entry.obj, entry.serializer );
entry.isDirty = false;
}
}
}
/**
* A class to store a cached entry.
*/
private class CacheEntry
{
long recid;
Object obj;
Serializer serializer;
boolean isDirty;
CacheEntry( long recid, Object obj, Serializer serializer, boolean isDirty )
{
this.recid = recid;
this.obj = obj;
this.serializer = serializer;
this.isDirty = isDirty;
}
} // class CacheEntry
private class CacheListener implements CachePolicyListener<CacheEntry>
{
/**
* Notification that cache is evicting an object
*
* @param obj object evicted from cache
*/
public void cacheObjectEvicted( CacheEntry obj ) throws CacheEvictionException
{
CacheEntry entry = obj;
if ( entry.isDirty )
{
try
{
recordManager.update( entry.recid, entry.obj, entry.serializer );
}
catch ( IOException except )
{
throw new CacheEvictionException( except );
}
}
}
}
}