351 lines
8.6 KiB
Java
351 lines
8.6 KiB
Java
|
/*******************************************************************************
|
||
|
* 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 org.apache.jdbm;
|
||
|
|
||
|
import javax.crypto.Cipher;
|
||
|
import java.io.IOException;
|
||
|
|
||
|
/**
|
||
|
* A DB wrapping and caching another DB.
|
||
|
*
|
||
|
* @author Jan Kotek
|
||
|
* @author Alex Boisvert
|
||
|
* @author Cees de Groot
|
||
|
*
|
||
|
* TODO add 'cache miss' statistics
|
||
|
*/
|
||
|
class DBCacheMRU
|
||
|
extends DBCache {
|
||
|
|
||
|
|
||
|
private static final boolean debug = false;
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Cached object hashtable
|
||
|
*/
|
||
|
protected LongHashMap<CacheEntry> _hash;
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Maximum number of objects in the cache.
|
||
|
*/
|
||
|
protected int _max;
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* 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 DB and
|
||
|
* using a given cache policy.
|
||
|
*/
|
||
|
public DBCacheMRU(String filename, boolean readonly, boolean transactionDisabled,
|
||
|
Cipher cipherIn, Cipher cipherOut, boolean useRandomAccessFile,
|
||
|
boolean deleteFilesAfterClose, int cacheMaxRecords, boolean lockingDisabled) {
|
||
|
super(filename, readonly, transactionDisabled,
|
||
|
cipherIn, cipherOut, useRandomAccessFile,
|
||
|
deleteFilesAfterClose,lockingDisabled);
|
||
|
|
||
|
_hash = new LongHashMap<CacheEntry>(cacheMaxRecords);
|
||
|
_max = cacheMaxRecords;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
public synchronized <A> A fetch(long recid, Serializer<A> serializer, boolean disableCache) throws IOException {
|
||
|
|
||
|
if (disableCache)
|
||
|
return super.fetch(recid, serializer, disableCache);
|
||
|
else
|
||
|
return fetch(recid, serializer);
|
||
|
}
|
||
|
|
||
|
|
||
|
public synchronized void delete(long recid)
|
||
|
throws IOException {
|
||
|
checkNotClosed();
|
||
|
|
||
|
super.delete(recid);
|
||
|
synchronized (_hash){
|
||
|
CacheEntry entry = _hash.get(recid);
|
||
|
if (entry != null) {
|
||
|
removeEntry(entry);
|
||
|
_hash.remove(entry._recid);
|
||
|
}
|
||
|
_hashDirties.remove(recid);
|
||
|
}
|
||
|
|
||
|
if(super.needsAutoCommit())
|
||
|
commit();
|
||
|
|
||
|
}
|
||
|
|
||
|
public synchronized <A> void update(final long recid, final A obj, final Serializer<A> serializer) throws IOException {
|
||
|
checkNotClosed();
|
||
|
|
||
|
synchronized (_hash){
|
||
|
|
||
|
//remove entry if it already exists
|
||
|
CacheEntry entry = cacheGet(recid);
|
||
|
if (entry != null) {
|
||
|
_hash.remove(recid);
|
||
|
removeEntry(entry);
|
||
|
}
|
||
|
|
||
|
//check if entry is in dirties, in this case just update its object
|
||
|
DirtyCacheEntry e = _hashDirties.get(recid);
|
||
|
if(e!=null){
|
||
|
if(recid!=e._recid) throw new Error();
|
||
|
e._obj = obj;
|
||
|
e._serializer = serializer;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//create new dirty entry
|
||
|
e = new DirtyCacheEntry();
|
||
|
e._recid = recid;
|
||
|
e._obj = obj;
|
||
|
e._serializer = serializer;
|
||
|
_hashDirties.put(recid,e);
|
||
|
}
|
||
|
|
||
|
if(super.needsAutoCommit())
|
||
|
commit();
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
public synchronized <A> A fetch(long recid, Serializer<A> serializer)
|
||
|
throws IOException {
|
||
|
|
||
|
checkNotClosed();
|
||
|
|
||
|
final CacheEntry entry = cacheGet(recid);
|
||
|
if (entry != null) {
|
||
|
return (A) entry._obj;
|
||
|
}
|
||
|
|
||
|
//check dirties
|
||
|
final DirtyCacheEntry entry2 = _hashDirties.get(recid);
|
||
|
if(entry2!=null){
|
||
|
return (A) entry2._obj;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
A value = super.fetch(recid, serializer);
|
||
|
|
||
|
if(super.needsAutoCommit())
|
||
|
commit();
|
||
|
|
||
|
|
||
|
//put record into MRU cache
|
||
|
cachePut(recid, value);
|
||
|
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
|
||
|
public synchronized void close() {
|
||
|
|
||
|
if(isClosed())
|
||
|
return;
|
||
|
|
||
|
updateCacheEntries();
|
||
|
super.close();
|
||
|
_hash = null;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
public synchronized void rollback() {
|
||
|
|
||
|
// discard all cache entries since we don't know which entries
|
||
|
// where part of the transaction
|
||
|
synchronized (_hash){
|
||
|
_hash.clear();
|
||
|
_first = null;
|
||
|
_last = null;
|
||
|
}
|
||
|
|
||
|
super.rollback();
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Obtain an object in the cache
|
||
|
*/
|
||
|
protected CacheEntry cacheGet(long key) {
|
||
|
synchronized (_hash){
|
||
|
CacheEntry entry = _hash.get(key);
|
||
|
if ( entry != null && _last != entry) {
|
||
|
//touch entry
|
||
|
removeEntry(entry);
|
||
|
addEntry(entry);
|
||
|
}
|
||
|
return entry;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Place an object in the cache.
|
||
|
*
|
||
|
* @throws IOException
|
||
|
*/
|
||
|
protected void cachePut(final long recid, final Object value) throws IOException {
|
||
|
synchronized (_hash){
|
||
|
CacheEntry entry = _hash.get(recid);
|
||
|
if (entry != null) {
|
||
|
entry._obj = value;
|
||
|
//touch entry
|
||
|
if (_last != entry) {
|
||
|
removeEntry(entry);
|
||
|
addEntry(entry);
|
||
|
}
|
||
|
} else {
|
||
|
|
||
|
if (_hash.size() >= _max) {
|
||
|
// purge and recycle entry
|
||
|
entry = purgeEntry();
|
||
|
entry._recid = recid;
|
||
|
entry._obj = value;
|
||
|
} else {
|
||
|
entry = new CacheEntry(recid, value);
|
||
|
}
|
||
|
addEntry(entry);
|
||
|
_hash.put(entry._recid, entry);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add a CacheEntry. Entry goes at the end of the list.
|
||
|
*/
|
||
|
protected void addEntry(CacheEntry entry) {
|
||
|
synchronized (_hash){
|
||
|
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) {
|
||
|
synchronized (_hash){
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Purge least recently used object from the cache
|
||
|
*
|
||
|
* @return recyclable CacheEntry
|
||
|
*/
|
||
|
protected CacheEntry purgeEntry() {
|
||
|
synchronized (_hash){
|
||
|
CacheEntry entry = _first;
|
||
|
if (entry == null)
|
||
|
return new CacheEntry(-1, null);
|
||
|
|
||
|
removeEntry(entry);
|
||
|
_hash.remove(entry._recid);
|
||
|
entry._obj = null;
|
||
|
return entry;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
@SuppressWarnings("unchecked")
|
||
|
static final class CacheEntry {
|
||
|
|
||
|
protected long _recid;
|
||
|
protected Object _obj;
|
||
|
|
||
|
|
||
|
protected CacheEntry _previous;
|
||
|
protected CacheEntry _next;
|
||
|
|
||
|
|
||
|
CacheEntry(long recid, Object obj) {
|
||
|
_recid = recid;
|
||
|
_obj = obj;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
public void clearCache() {
|
||
|
if(debug)
|
||
|
System.err.println("DBCache: Clear cache");
|
||
|
|
||
|
// discard all cache entries since we don't know which entries
|
||
|
// where part of the transaction
|
||
|
synchronized (_hash){
|
||
|
_hash.clear();
|
||
|
_first = null;
|
||
|
_last = null;
|
||
|
|
||
|
//clear dirties
|
||
|
updateCacheEntries();
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|