/* This file is part of the db4o object database http://www.db4o.com Copyright (C) 2004 - 2011 Versant Corporation http://www.versant.com db4o is free software; you can redistribute it and/or modify it under the terms of version 3 of the GNU General Public License as published by the Free Software Foundation. db4o is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. */ package com.db4o.defragment; import java.io.*; import com.db4o.*; import com.db4o.config.*; import com.db4o.ext.*; import com.db4o.foundation.*; import com.db4o.internal.*; import com.db4o.internal.CommitTimestampSupport.*; import com.db4o.internal.btree.*; import com.db4o.internal.classindex.*; import com.db4o.internal.encoding.*; import com.db4o.internal.ids.*; import com.db4o.internal.mapping.*; import com.db4o.internal.marshall.*; import com.db4o.internal.slots.*; import com.db4o.io.*; import com.db4o.typehandlers.*; /** * @exclude */ public class DefragmentServicesImpl implements DefragmentServices { public static abstract class DbSelector { DbSelector() { } abstract LocalObjectContainer db(DefragmentServicesImpl context); Transaction transaction(DefragmentServicesImpl context) { return db(context).systemTransaction(); } } public final static DbSelector SOURCEDB=new DbSelector() { LocalObjectContainer db(DefragmentServicesImpl context) { return context._sourceDb; } }; public final static DbSelector TARGETDB=new DbSelector() { LocalObjectContainer db(DefragmentServicesImpl context) { return context._targetDb; } }; private final LocalObjectContainer _sourceDb; private final LocalObjectContainer _targetDb; private final IdMapping _mapping; private DefragmentListener _listener; private Queue4 _unindexed=new NonblockingQueue(); private DefragmentConfig _defragConfig; public DefragmentServicesImpl(DefragmentConfig defragConfig,DefragmentListener listener) throws IOException { _listener=listener; Config4Impl originalConfig = (Config4Impl) defragConfig.db4oConfig(); Storage storage = defragConfig.backupStorage(); if(defragConfig.readOnly()){ storage = new NonFlushingStorage(storage); } Config4Impl sourceConfig = prepareConfig(originalConfig, storage, defragConfig.readOnly()); _sourceDb = (LocalObjectContainer)Db4o.openFile(sourceConfig,defragConfig.tempPath()).ext(); _sourceDb.showInternalClasses(true); defragConfig.db4oConfig().blockSize(_sourceDb.blockSize()); if (!originalConfig.generateCommitTimestamps().definiteNo()) { defragConfig.db4oConfig().generateCommitTimestamps(_sourceDb.config().generateCommitTimestamps().definiteYes()); } _targetDb = freshTargetFile(defragConfig); _mapping=defragConfig.mapping(); _mapping.open(); _defragConfig = defragConfig; } private Config4Impl prepareConfig(Config4Impl originalConfig, Storage storage, boolean readOnly) { Config4Impl sourceConfig=(Config4Impl) originalConfig.deepClone(null); sourceConfig.weakReferences(false); sourceConfig.storage(storage); sourceConfig.readOnly(readOnly); return sourceConfig; } static LocalObjectContainer freshTempFile(String fileName,int blockSize) throws IOException { Storage storage = Platform4.newStorage(); storage.delete(fileName); Configuration db4oConfig = DefragmentConfig.vanillaDb4oConfig(blockSize); db4oConfig.objectClass(IdSlotMapping.class).objectField("_id").indexed(true); db4oConfig.storage(storage); return (LocalObjectContainer)Db4o.openFile(db4oConfig,fileName).ext(); } static LocalObjectContainer freshTargetFile(DefragmentConfig config) throws IOException { config.db4oConfig().storage().delete(config.origPath()); Configuration clonedDb4oConfig = config.clonedDb4oConfig(); clonedDb4oConfig.readOnly(false); return (LocalObjectContainer) Db4o.openFile(clonedDb4oConfig,config.origPath()); } public int mappedID(int oldID,int defaultID) { int mapped=internalMappedID(oldID); return (mapped!=0 ? mapped : defaultID); } public int strictMappedID(int oldID) throws MappingNotFoundException { int mapped=internalMappedID(oldID); if(mapped==0) { throw new MappingNotFoundException(oldID); } return mapped; } public int mappedID(int id) { if(id == 0){ return 0; } int mapped = internalMappedID(id); if(mapped==0) { _listener.notifyDefragmentInfo(new DefragmentInfo("No mapping found for ID "+id)); return Const4.INVALID_OBJECT_ID; } return mapped; } private int internalMappedID(int oldID) throws MappingNotFoundException { if(oldID==0) { return 0; } int mappedId = _mapping.mappedId(oldID); if(mappedId == 0 && _sourceDb.handlers().isSystemHandler(oldID)){ return oldID; } return mappedId; } public void mapIDs(int oldID,int newID, boolean isClassID) { _mapping.mapId(oldID,newID, isClassID); } public void close() { _sourceDb.close(); _targetDb.close(); _mapping.close(); } public ByteArrayBuffer bufferByID(DbSelector selector,int id) { Slot slot=committedSlot(selector, id); return bufferByAddress(selector,slot.address(),slot.length()); } private Slot committedSlot(DbSelector selector, int id) { return selector.db(this).idSystem().committedSlot(id); } public ByteArrayBuffer sourceBufferByAddress(int address,int length) throws IOException { return bufferByAddress(SOURCEDB, address, length); } public ByteArrayBuffer targetBufferByAddress(int address,int length) throws IOException { return bufferByAddress(TARGETDB, address, length); } public ByteArrayBuffer bufferByAddress(DbSelector selector,int address,int length) { return selector.db(this).decryptedBufferByAddress(address,length); } public StatefulBuffer targetStatefulBufferByAddress(int address,int length) throws IllegalArgumentException { return _targetDb.readWriterByAddress(TARGETDB.transaction(this),address,length); } public Slot allocateTargetSlot(int length) { return _targetDb.allocateSlot(length); } public void targetWriteBytes(DefragmentContextImpl context,int address) { context.write(_targetDb,address); } public void targetWriteBytes(ByteArrayBuffer reader,int address) { _targetDb.writeBytes(reader,address,0); } public StoredClass[] storedClasses(DbSelector selector) { LocalObjectContainer db = selector.db(this); db.showInternalClasses(true); try { return db.classCollection().storedClasses(); } finally { db.showInternalClasses(false); } } public LatinStringIO stringIO() { return _sourceDb.stringIO(); } public void targetCommit() { _targetDb.commit(); } public TypeHandler4 sourceHandler(int id) { return _sourceDb.typeHandlerForClassMetadataID(id); } public int sourceClassCollectionID() { return _sourceDb.classCollection().getID(); } private Hashtable4 _classIndices=new Hashtable4(16); public int classIndexID(ClassMetadata classMetadata) { return classIndex(classMetadata).id(); } public void traverseAll(ClassMetadata classMetadata, Visitor4 command) { if(!classMetadata.hasClassIndex()) { return; } classMetadata.index().traverseIds(SOURCEDB.transaction(this), command); } public void traverseAllIndexSlots(ClassMetadata classMetadata,Visitor4 command) { Iterator4 slotIDIter=classMetadata.index().allSlotIDs(SOURCEDB.transaction(this)); while(slotIDIter.moveNext()) { command.visit(slotIDIter.current()); } } public void traverseAllIndexSlots(BTree btree,Visitor4 command) { Iterator4 slotIDIter=btree.allNodeIds(SOURCEDB.transaction(this)); while(slotIDIter.moveNext()) { command.visit(slotIDIter.current()); } } public void registerBTreeIDs(BTree btree, final IDMappingCollector collector) { collector.createIDMapping(this, btree.getID(), false); traverseAllIndexSlots(btree, new Visitor4() { public void visit(Object obj) { int id=((Integer)obj).intValue(); collector.createIDMapping(DefragmentServicesImpl.this, id, false); } }); } public int databaseIdentityID(DbSelector selector) { LocalObjectContainer db = selector.db(this); Db4oDatabase identity = db.identity(); if(identity==null) { return 0; } return identity.getID(selector.transaction(this)); } private ClassIndexStrategy classIndex(ClassMetadata classMetadata) { ClassIndexStrategy classIndex=(ClassIndexStrategy)_classIndices.get(classMetadata); if(classIndex==null) { classIndex=new BTreeClassIndexStrategy(classMetadata); _classIndices.put(classMetadata,classIndex); classIndex.initialize(_targetDb); } return classIndex; } public Transaction systemTrans() { return SOURCEDB.transaction(this); } public void copyIdentity() { _targetDb.setIdentity(_sourceDb.identity()); } public void replaceClassMetadataRepository() { Transaction systemTransaction = _targetDb.systemTransaction(); // Can't use strictMappedID because the repository ID can // be lower than HandlerRegisrtry _highestBuiltinTypeID and // the ClassRepository ID would be treated as a system handler // and the unmapped ID would be returned. int newRepositoryId = _mapping.mappedId(sourceClassCollectionID()); int sourceIdentityID = databaseIdentityID(DefragmentServicesImpl.SOURCEDB); int targetIdentityID = _mapping.mappedId(sourceIdentityID); int targetUuidIndexID = _mapping.mappedId(sourceUuidIndexID()); int oldIdentityId = _targetDb.systemData().identity().getID(systemTransaction); int oldRepositoryId = _targetDb.classCollection().getID(); ClassMetadataRepository oldRepository = _targetDb.classCollection(); ClassMetadataRepository newRepository = new ClassMetadataRepository(systemTransaction); newRepository.setID(newRepositoryId); newRepository.read(systemTransaction); newRepository.initOnUp(systemTransaction); _targetDb.systemData().classCollectionID(newRepositoryId); _targetDb.replaceClassMetadataRepository(newRepository); _targetDb.systemData().uuidIndexId(targetUuidIndexID); Db4oDatabase identity = (Db4oDatabase) _targetDb.getByID(systemTransaction, targetIdentityID); _targetDb.setIdentity(identity); ClassMetadataIterator iterator = oldRepository.iterator(); while(iterator.moveNext()){ ClassMetadata classMetadata = iterator.currentClass(); BTreeClassIndexStrategy index = (BTreeClassIndexStrategy) classMetadata.index(); index.btree().free(_targetDb.localSystemTransaction()); freeById(classMetadata.getID()); } freeById(oldIdentityId); freeById(oldRepositoryId); } public void defragIdToTimestampBtree() { if (_sourceDb.systemData().idToTimestampIndexId() == 0) { return; } final LocalTransaction targetTransaction = (LocalTransaction)_targetDb.systemTransaction(); final LocalTransaction sourceTransaction = (LocalTransaction)_sourceDb.systemTransaction(); final CommitTimestampSupport target = targetTransaction.commitTimestampSupport(); final CommitTimestampSupport source = sourceTransaction.commitTimestampSupport(); if (source.idToTimestamp() == null) { return; } source.idToTimestamp().traverseKeys(sourceTransaction, new Visitor4<TimestampEntry>() { public void visit(TimestampEntry te) { int mappedID = mappedID(te.parentID()); target.put(targetTransaction, mappedID, te.getCommitTimestamp()); } }); } private void freeById(int id){ _targetDb.systemTransaction().idSystem().notifySlotDeleted(id, SlotChangeFactory.SYSTEM_OBJECTS); } public ByteArrayBuffer sourceBufferByID(int sourceID) { return bufferByID(SOURCEDB,sourceID); } public BTree sourceUuidIndex() { if(sourceUuidIndexID()==0) { return null; } return _sourceDb.uUIDIndex().getIndex(systemTrans()); } public void targetUuidIndexID(int id) { _targetDb.systemData().uuidIndexId(id); } public int sourceUuidIndexID() { return _sourceDb.systemData().uuidIndexId(); } public int sourceIdToTimestampIndexID() { return _sourceDb.systemData().idToTimestampIndexId(); } public ClassMetadata classMetadataForId(int id) { return _sourceDb.classMetadataForID(id); } public void registerUnindexed(int id) { _unindexed.add(new Integer(id)); } public IdSource unindexedIDs() { return new IdSource(_unindexed); } public ObjectHeader sourceObjectHeader(ByteArrayBuffer buffer) { return new ObjectHeader(_sourceDb, buffer); } public int blockSize() { return _sourceDb.blockSize(); } public int sourceAddressByID(int sourceID) { return committedSlot(SOURCEDB, sourceID).address(); } public int targetAddressByID(int sourceID) { return _mapping.addressForId(sourceID); } public boolean accept(StoredClass klass) { return this._defragConfig.storedClassFilter().accept(klass); } public int targetNewId() { return _targetDb.idSystem().newId(); } public IdMapping mapping(){ return _mapping; } public void commitIds(){ FreespaceCommitter freespaceCommitter = new FreespaceCommitter(_targetDb.freespaceManager()); freespaceCommitter.transactionalIdSystem(systemTrans().idSystem()); _targetDb.idSystem().commit(mapping().slotChanges(), freespaceCommitter); freespaceCommitter.commit(); } }