/******************************************************************************* * 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); } } }