591 lines
20 KiB
Java
591 lines
20 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 java.io.DataInput;
|
|
import java.io.DataOutput;
|
|
import java.io.IOError;
|
|
import java.io.IOException;
|
|
import java.lang.ref.WeakReference;
|
|
import java.util.*;
|
|
import java.util.concurrent.ConcurrentMap;
|
|
import java.util.concurrent.ConcurrentNavigableMap;
|
|
|
|
/**
|
|
* An abstract class implementing most of DB.
|
|
* It also has some JDBM package protected stuff (getNamedRecord)
|
|
*/
|
|
abstract class DBAbstract implements DB {
|
|
|
|
|
|
/**
|
|
* Reserved slot for name directory recid.
|
|
*/
|
|
static final byte NAME_DIRECTORY_ROOT = 0;
|
|
|
|
|
|
/**
|
|
* Reserved slot for version number
|
|
*/
|
|
static final byte STORE_VERSION_NUMBER_ROOT = 1;
|
|
|
|
/**
|
|
* Reserved slot for recid where Serial class info is stored
|
|
*
|
|
* NOTE when introducing more roots, do not forget to update defrag
|
|
*/
|
|
static final byte SERIAL_CLASS_INFO_RECID_ROOT = 2;
|
|
|
|
/** to prevent double instances of the same collection, we use weak value map
|
|
*
|
|
* //TODO what to do when there is rollback?
|
|
* //TODO clear on close
|
|
*/
|
|
final private Map<String,WeakReference<Object>> collections = new HashMap<String,WeakReference<Object>>();
|
|
|
|
|
|
|
|
/**
|
|
* 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 java.io.IOException when one of the underlying I/O operations fails.
|
|
*/
|
|
abstract <A> long insert(A obj, Serializer<A> serializer,boolean disableCache) throws IOException;
|
|
|
|
/**
|
|
* Deletes a record.
|
|
*
|
|
* @param recid the rowid for the record that should be deleted.
|
|
* @throws java.io.IOException when one of the underlying I/O operations fails.
|
|
*/
|
|
abstract void delete(long recid) throws IOException;
|
|
|
|
|
|
/**
|
|
* Updates a record using a custom serializer.
|
|
* If given recid does not exist, IOException will be thrown before/during commit (cache).
|
|
*
|
|
* @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 java.io.IOException when one of the underlying I/O operations fails
|
|
*/
|
|
abstract <A> void update(long recid, A obj, Serializer<A> serializer)
|
|
throws IOException;
|
|
|
|
|
|
/**
|
|
* 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, null if given recid does not exist
|
|
* @throws java.io.IOException when one of the underlying I/O operations fails.
|
|
*/
|
|
abstract <A> A fetch(long recid, Serializer<A> serializer)
|
|
throws IOException;
|
|
|
|
/**
|
|
* Fetches a record using a custom serializer and optionaly disabled cache
|
|
*
|
|
* @param recid the recid for the record that must be fetched.
|
|
* @param serializer a custom serializer
|
|
* @param disableCache true to disable any caching mechanism
|
|
* @return the object contained in the record, null if given recid does not exist
|
|
* @throws java.io.IOException when one of the underlying I/O operations fails.
|
|
*/
|
|
abstract <A> A fetch(long recid, Serializer<A> serializer, boolean disableCache)
|
|
throws IOException;
|
|
|
|
|
|
public long insert(Object obj) throws IOException {
|
|
return insert(obj, defaultSerializer(),false);
|
|
}
|
|
|
|
|
|
public void update(long recid, Object obj) throws IOException {
|
|
update(recid, obj, defaultSerializer());
|
|
}
|
|
|
|
|
|
synchronized public <A> A fetch(long recid) throws IOException {
|
|
return (A) fetch(recid, defaultSerializer());
|
|
}
|
|
|
|
synchronized public <K, V> ConcurrentMap<K, V> getHashMap(String name) {
|
|
Object o = getCollectionInstance(name);
|
|
if(o!=null)
|
|
return (ConcurrentMap<K, V>) o;
|
|
|
|
try {
|
|
long recid = getNamedObject(name);
|
|
if(recid == 0) return null;
|
|
|
|
HTree tree = fetch(recid);
|
|
tree.setPersistenceContext(this);
|
|
if(!tree.hasValues()){
|
|
throw new ClassCastException("HashSet is not HashMap");
|
|
}
|
|
collections.put(name,new WeakReference<Object>(tree));
|
|
return tree;
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}
|
|
}
|
|
|
|
synchronized public <K, V> ConcurrentMap<K, V> createHashMap(String name) {
|
|
return createHashMap(name, null, null);
|
|
}
|
|
|
|
|
|
public synchronized <K, V> ConcurrentMap<K, V> createHashMap(String name, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
|
|
try {
|
|
assertNameNotExist(name);
|
|
|
|
HTree<K, V> tree = new HTree(this, keySerializer, valueSerializer,true);
|
|
long recid = insert(tree);
|
|
setNamedObject(name, recid);
|
|
collections.put(name,new WeakReference<Object>(tree));
|
|
return tree;
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}
|
|
}
|
|
|
|
public synchronized <K> Set<K> getHashSet(String name) {
|
|
Object o = getCollectionInstance(name);
|
|
if(o!=null)
|
|
return (Set<K>) o;
|
|
|
|
try {
|
|
long recid = getNamedObject(name);
|
|
if(recid == 0) return null;
|
|
|
|
HTree tree = fetch(recid);
|
|
tree.setPersistenceContext(this);
|
|
if(tree.hasValues()){
|
|
throw new ClassCastException("HashMap is not HashSet");
|
|
}
|
|
Set<K> ret = new HTreeSet(tree);
|
|
collections.put(name,new WeakReference<Object>(ret));
|
|
return ret;
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}
|
|
}
|
|
|
|
public synchronized <K> Set<K> createHashSet(String name) {
|
|
return createHashSet(name, null);
|
|
}
|
|
|
|
public synchronized <K> Set<K> createHashSet(String name, Serializer<K> keySerializer) {
|
|
try {
|
|
assertNameNotExist(name);
|
|
|
|
HTree<K, Object> tree = new HTree(this, keySerializer, null,false);
|
|
long recid = insert(tree);
|
|
setNamedObject(name, recid);
|
|
|
|
Set<K> ret = new HTreeSet<K>(tree);
|
|
collections.put(name,new WeakReference<Object>(ret));
|
|
return ret;
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}
|
|
}
|
|
|
|
synchronized public <K, V> ConcurrentNavigableMap<K, V> getTreeMap(String name) {
|
|
Object o = getCollectionInstance(name);
|
|
if(o!=null)
|
|
return (ConcurrentNavigableMap<K, V> ) o;
|
|
|
|
try {
|
|
long recid = getNamedObject(name);
|
|
if(recid == 0) return null;
|
|
|
|
BTree t = BTree.<K, V>load(this, recid);
|
|
if(!t.hasValues())
|
|
throw new ClassCastException("TreeSet is not TreeMap");
|
|
ConcurrentNavigableMap<K,V> ret = new BTreeMap<K, V>(t,false); //TODO put readonly flag here
|
|
collections.put(name,new WeakReference<Object>(ret));
|
|
return ret;
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}
|
|
}
|
|
|
|
synchronized public <K extends Comparable, V> ConcurrentNavigableMap<K, V> createTreeMap(String name) {
|
|
return createTreeMap(name, null, null, null);
|
|
}
|
|
|
|
|
|
public synchronized <K, V> ConcurrentNavigableMap<K, V> createTreeMap(String name,
|
|
Comparator<K> keyComparator,
|
|
Serializer<K> keySerializer,
|
|
Serializer<V> valueSerializer) {
|
|
try {
|
|
assertNameNotExist(name);
|
|
BTree<K, V> tree = BTree.createInstance(this, keyComparator, keySerializer, valueSerializer,true);
|
|
setNamedObject(name, tree.getRecid());
|
|
ConcurrentNavigableMap<K,V> ret = new BTreeMap<K, V>(tree,false); //TODO put readonly flag here
|
|
collections.put(name,new WeakReference<Object>(ret));
|
|
return ret;
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}
|
|
}
|
|
|
|
|
|
public synchronized <K> NavigableSet<K> getTreeSet(String name) {
|
|
Object o = getCollectionInstance(name);
|
|
if(o!=null)
|
|
return (NavigableSet<K> ) o;
|
|
|
|
try {
|
|
long recid = getNamedObject(name);
|
|
if(recid == 0) return null;
|
|
|
|
BTree t = BTree.<K, Object>load(this, recid);
|
|
if(t.hasValues())
|
|
throw new ClassCastException("TreeMap is not TreeSet");
|
|
BTreeSet<K> ret = new BTreeSet<K>(new BTreeMap(t,false));
|
|
collections.put(name,new WeakReference<Object>(ret));
|
|
return ret;
|
|
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}
|
|
}
|
|
|
|
public synchronized <K> NavigableSet<K> createTreeSet(String name) {
|
|
return createTreeSet(name, null, null);
|
|
}
|
|
|
|
|
|
public synchronized <K> NavigableSet<K> createTreeSet(String name, Comparator<K> keyComparator, Serializer<K> keySerializer) {
|
|
try {
|
|
assertNameNotExist(name);
|
|
BTree<K, Object> tree = BTree.createInstance(this, keyComparator, keySerializer, null,false);
|
|
setNamedObject(name, tree.getRecid());
|
|
BTreeSet<K> ret = new BTreeSet<K>(new BTreeMap(tree,false));
|
|
collections.put(name,new WeakReference<Object>(ret));
|
|
return ret;
|
|
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
synchronized public <K> List<K> createLinkedList(String name) {
|
|
return createLinkedList(name, null);
|
|
}
|
|
|
|
synchronized public <K> List<K> createLinkedList(String name, Serializer<K> serializer) {
|
|
try {
|
|
assertNameNotExist(name);
|
|
|
|
//allocate record and overwrite it
|
|
|
|
LinkedList2<K> list = new LinkedList2<K>(this, serializer);
|
|
long recid = insert(list);
|
|
setNamedObject(name, recid);
|
|
|
|
collections.put(name,new WeakReference<Object>(list));
|
|
|
|
return list;
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}
|
|
}
|
|
|
|
synchronized public <K> List<K> getLinkedList(String name) {
|
|
Object o = getCollectionInstance(name);
|
|
if(o!=null)
|
|
return (List<K> ) o;
|
|
|
|
try {
|
|
long recid = getNamedObject(name);
|
|
if(recid == 0) return null;
|
|
LinkedList2<K> list = (LinkedList2<K>) fetch(recid);
|
|
list.setPersistenceContext(this);
|
|
collections.put(name,new WeakReference<Object>(list));
|
|
return list;
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}
|
|
}
|
|
|
|
private synchronized Object getCollectionInstance(String name){
|
|
WeakReference ref = collections.get(name);
|
|
if(ref==null)return null;
|
|
Object o = ref.get();
|
|
if(o != null) return o;
|
|
//already GCed
|
|
collections.remove(name);
|
|
return null;
|
|
}
|
|
|
|
|
|
private void assertNameNotExist(String name) throws IOException {
|
|
if (getNamedObject(name) != 0)
|
|
throw new IllegalArgumentException("Object with name '" + name + "' already exists");
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Obtain the record id of a named object. Returns 0 if named object
|
|
* doesn't exist.
|
|
* Named objects are used to store Map views and other well known objects.
|
|
*/
|
|
synchronized protected long getNamedObject(String name) throws IOException{
|
|
long nameDirectory_recid = getRoot(NAME_DIRECTORY_ROOT);
|
|
if(nameDirectory_recid == 0){
|
|
return 0;
|
|
}
|
|
HTree<String,Long> m = fetch(nameDirectory_recid);
|
|
Long res = m.get(name);
|
|
if(res == null)
|
|
return 0;
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the record id of a named object.
|
|
* Named objects are used to store Map views and other well known objects.
|
|
*/
|
|
synchronized protected void setNamedObject(String name, long recid) throws IOException{
|
|
long nameDirectory_recid = getRoot(NAME_DIRECTORY_ROOT);
|
|
HTree<String,Long> m = null;
|
|
if(nameDirectory_recid == 0){
|
|
//does not exists, create it
|
|
m = new HTree<String, Long>(this,null,null,true);
|
|
nameDirectory_recid = insert(m);
|
|
setRoot(NAME_DIRECTORY_ROOT,nameDirectory_recid);
|
|
}else{
|
|
//fetch it
|
|
m = fetch(nameDirectory_recid);
|
|
}
|
|
m.put(name,recid);
|
|
}
|
|
|
|
|
|
|
|
|
|
synchronized public Map<String,Object> getCollections(){
|
|
try{
|
|
Map<String,Object> ret = new LinkedHashMap<String, Object>();
|
|
long nameDirectory_recid = getRoot(NAME_DIRECTORY_ROOT);
|
|
if(nameDirectory_recid==0)
|
|
return ret;
|
|
HTree<String,Long> m = fetch(nameDirectory_recid);
|
|
|
|
for(Map.Entry<String,Long> e:m.entrySet()){
|
|
Object o = fetch(e.getValue());
|
|
if(o instanceof BTree){
|
|
if(((BTree) o).hasValues)
|
|
o = getTreeMap(e.getKey());
|
|
else
|
|
o = getTreeSet(e.getKey());
|
|
}
|
|
else if( o instanceof HTree){
|
|
if(((HTree) o).hasValues)
|
|
o = getHashMap(e.getKey());
|
|
else
|
|
o = getHashSet(e.getKey());
|
|
}
|
|
|
|
ret.put(e.getKey(), o);
|
|
}
|
|
return Collections.unmodifiableMap(ret);
|
|
}catch(IOException e){
|
|
throw new IOError(e);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
synchronized public void deleteCollection(String name){
|
|
try{
|
|
long nameDirectory_recid = getRoot(NAME_DIRECTORY_ROOT);
|
|
if(nameDirectory_recid==0)
|
|
throw new IOException("Collection not found");
|
|
HTree<String,Long> dir = fetch(nameDirectory_recid);
|
|
|
|
Long recid = dir.get(name);
|
|
if(recid == null) throw new IOException("Collection not found");
|
|
|
|
Object o = fetch(recid);
|
|
//we can not use O instance since it is not correctly initialized
|
|
if(o instanceof LinkedList2){
|
|
LinkedList2 l = (LinkedList2) o;
|
|
l.clear();
|
|
delete(l.rootRecid);
|
|
}else if(o instanceof BTree){
|
|
((BTree) o).clear();
|
|
} else if( o instanceof HTree){
|
|
HTree t = (HTree) o;
|
|
t.clear();
|
|
HTreeDirectory n = (HTreeDirectory) fetch(t.rootRecid,t.SERIALIZER);
|
|
n.deleteAllChildren();
|
|
delete(t.rootRecid);
|
|
}else{
|
|
throw new InternalError("unknown collection type: "+(o==null?null:o.getClass()));
|
|
}
|
|
delete(recid);
|
|
collections.remove(name);
|
|
|
|
|
|
dir.remove(name);
|
|
|
|
}catch(IOException e){
|
|
throw new IOError(e);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/** we need to set reference to this DB instance, so serializer needs to be here*/
|
|
final Serializer<Serialization> defaultSerializationSerializer = new Serializer<Serialization>(){
|
|
|
|
public void serialize(DataOutput out, Serialization obj) throws IOException {
|
|
LongPacker.packLong(out,obj.serialClassInfoRecid);
|
|
SerialClassInfo.serializer.serialize(out,obj.registered);
|
|
}
|
|
|
|
public Serialization deserialize(DataInput in) throws IOException, ClassNotFoundException {
|
|
final long recid = LongPacker.unpackLong(in);
|
|
final ArrayList<SerialClassInfo.ClassInfo> classes = SerialClassInfo.serializer.deserialize(in);
|
|
return new Serialization(DBAbstract.this,recid,classes);
|
|
}
|
|
};
|
|
|
|
public synchronized Serializer defaultSerializer() {
|
|
|
|
try{
|
|
long serialClassInfoRecid = getRoot(SERIAL_CLASS_INFO_RECID_ROOT);
|
|
if (serialClassInfoRecid == 0) {
|
|
//allocate new recid
|
|
serialClassInfoRecid = insert(null,Utils.NULL_SERIALIZER,false);
|
|
//and insert new serializer
|
|
Serialization ser = new Serialization(this,serialClassInfoRecid,new ArrayList<SerialClassInfo.ClassInfo>());
|
|
|
|
update(serialClassInfoRecid,ser, defaultSerializationSerializer);
|
|
setRoot(SERIAL_CLASS_INFO_RECID_ROOT, serialClassInfoRecid);
|
|
return ser;
|
|
}else{
|
|
return fetch(serialClassInfoRecid,defaultSerializationSerializer);
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
final protected void checkNotClosed(){
|
|
if(isClosed()) throw new IllegalStateException("db was closed");
|
|
}
|
|
|
|
protected abstract void setRoot(byte root, long recid);
|
|
protected abstract long getRoot(byte root);
|
|
|
|
|
|
synchronized public long collectionSize(Object collection){
|
|
if(collection instanceof BTreeMap){
|
|
BTreeMap t = (BTreeMap) collection;
|
|
if(t.fromKey!=null|| t.toKey!=null) throw new IllegalArgumentException("collectionSize does not work on BTree submap");
|
|
return t.tree._entries;
|
|
}else if(collection instanceof HTree){
|
|
return ((HTree)collection).getRoot().size;
|
|
}else if(collection instanceof HTreeSet){
|
|
return collectionSize(((HTreeSet) collection).map);
|
|
}else if(collection instanceof BTreeSet){
|
|
return collectionSize(((BTreeSet) collection).map);
|
|
}else if(collection instanceof LinkedList2){
|
|
return ((LinkedList2)collection).getRoot().size;
|
|
}else{
|
|
throw new IllegalArgumentException("Not JDBM collection");
|
|
}
|
|
}
|
|
|
|
|
|
void addShutdownHook(){
|
|
if(shutdownCloseThread!=null){
|
|
shutdownCloseThread = new ShutdownCloseThread();
|
|
Runtime.getRuntime().addShutdownHook(shutdownCloseThread);
|
|
}
|
|
}
|
|
|
|
public void close(){
|
|
if(shutdownCloseThread!=null){
|
|
Runtime.getRuntime().removeShutdownHook(shutdownCloseThread);
|
|
shutdownCloseThread.dbToClose = null;
|
|
shutdownCloseThread = null;
|
|
}
|
|
}
|
|
|
|
|
|
ShutdownCloseThread shutdownCloseThread = null;
|
|
|
|
private static class ShutdownCloseThread extends Thread{
|
|
|
|
DBAbstract dbToClose = null;
|
|
|
|
ShutdownCloseThread(){
|
|
super("JDBM shutdown");
|
|
}
|
|
|
|
public void run(){
|
|
if(dbToClose!=null && !dbToClose.isClosed()){
|
|
dbToClose.shutdownCloseThread = null;
|
|
dbToClose.close();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
synchronized public void rollback() {
|
|
try {
|
|
for(WeakReference<Object> o:collections.values()){
|
|
Object c = o.get();
|
|
if(c != null && c instanceof BTreeMap){
|
|
//reload tree
|
|
BTreeMap m = (BTreeMap) c;
|
|
m.tree = fetch(m.tree.getRecid());
|
|
}
|
|
if(c != null && c instanceof BTreeSet){
|
|
//reload tree
|
|
BTreeSet m = (BTreeSet) c;
|
|
m.map.tree = fetch(m.map.tree.getRecid());
|
|
}
|
|
|
|
|
|
}
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}
|
|
|
|
}
|
|
}
|