/* 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.io; import com.db4o.ext.*; import com.db4o.foundation.*; import com.db4o.foundation.io.*; /** * Storage that allows to save an open database file * as another file while keeping the reference system * in place. If anything goes wrong during copying the * storage tries to reopen the original file, so commit * operations can still take place against the original * file. */ public class SaveAsStorage extends StorageDecorator { private final Hashtable4 _binRecords = new Hashtable4(); public SaveAsStorage(Storage storage) { super(storage); } /** * call this method to save the content of a currently * open ObjectContainer session to a new file location. * Invocation will close the old file without a commit, * keep the reference system in place and connect it to * the file in the new location. If anything goes wrong * during the copying operation or while opening it will * be attempted to reopen the old file. In this case a * Db4oException will be thrown. * @param oldUri the path to the old open database file * @param newUri the path to the new database file */ public void saveAs(final String oldUri, final String newUri) { if(File4.exists(newUri)){ throw new IllegalStateException(newUri + " already exists"); } BinRecord binRecord = (BinRecord) _binRecords.get(oldUri); if(binRecord == null){ throw new IllegalStateException(oldUri + " was never opened or was closed."); } final BinConfiguration oldConfiguration = binRecord._binConfiguration; final SaveAsBin saveAsBin = binRecord._bin; Runnable closure = new Runnable() { public void run() { saveAsBin.sync(); saveAsBin.close(); try { File4.copy(oldUri, newUri); } catch (Exception e) { reopenOldConfiguration(saveAsBin, oldConfiguration, newUri, e); } BinConfiguration newConfiguration = pointToNewUri(oldConfiguration,newUri); try{ Bin newBin = _storage.open(newConfiguration); saveAsBin.delegateTo(newBin); _binRecords.remove(oldUri); _binRecords.put(newUri, new BinRecord(newConfiguration, saveAsBin)); } catch(Exception e){ reopenOldConfiguration(saveAsBin, oldConfiguration, newUri, e); } }}; saveAsBin.exchangeUnderlyingBin(closure); } private BinConfiguration pointToNewUri(BinConfiguration oldConfig, String newUri) { return new BinConfiguration( newUri, oldConfig.lockFile(), oldConfig.initialLength(), oldConfig.readOnly()); } private void reopenOldConfiguration(SaveAsBin saveAsBin, BinConfiguration config, String newUri, Exception e) { Bin safeBin = _storage.open(config); saveAsBin.delegateTo(safeBin); throw new Db4oException("Copying to " + newUri + " failed. Reopened " + config.uri(), e); } @Override public Bin open(BinConfiguration config) throws Db4oIOException { SaveAsBin openedBin = new SaveAsBin(super.open(config)); _binRecords.put(config.uri(), new BinRecord(config, openedBin)); return openedBin; } private static class BinRecord { final SaveAsBin _bin; final BinConfiguration _binConfiguration; BinRecord(BinConfiguration binConfiguration, SaveAsBin bin){ _binConfiguration = binConfiguration; _bin = bin; } } /** * We could have nicely used BinDecorator here, but * BinDecorator doesn't allow exchanging the Bin. To * be compatible with released versions we do */ private static class SaveAsBin implements Bin{ private Bin _bin; SaveAsBin(Bin delegate_){ _bin = delegate_; } public void exchangeUnderlyingBin(Runnable closure) { synchronized (this) { closure.run(); } } public void close() { synchronized (this) { _bin.close(); } } public long length() { synchronized (this) { return _bin.length(); } } public int read(long position, byte[] bytes, int bytesToRead) { synchronized (this) { return _bin.read(position, bytes, bytesToRead); } } public void sync() { synchronized (this) { _bin.sync(); } } public void sync(Runnable runnable) { synchronized (this) { sync(); runnable.run(); sync(); } } public int syncRead(long position, byte[] bytes, int bytesToRead) { synchronized (this) { return _bin.syncRead(position, bytes, bytesToRead); } } public void write(long position, byte[] bytes, int bytesToWrite) { synchronized (this) { _bin.write(position, bytes, bytesToWrite); } } public void delegateTo(Bin bin){ _bin = bin; } } }