001: /* Copyright (C) 2004 - 2007 db4objects Inc. http://www.db4o.com
002:
003: This file is part of the db4o open source object database.
004:
005: db4o is free software; you can redistribute it and/or modify it under
006: the terms of version 2 of the GNU General Public License as published
007: by the Free Software Foundation and as clarified by db4objects' GPL
008: interpretation policy, available at
009: http://www.db4o.com/about/company/legalpolicies/gplinterpretation/
010: Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street,
011: Suite 350, San Mateo, CA 94403, USA.
012:
013: db4o is distributed in the hope that it will be useful, but WITHOUT ANY
014: WARRANTY; without even the implied warranty of MERCHANTABILITY or
015: FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
016: for more details.
017:
018: You should have received a copy of the GNU General Public License along
019: with this program; if not, write to the Free Software Foundation, Inc.,
020: 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
021: package com.db4o.defragment;
022:
023: import java.io.*;
024:
025: import com.db4o.*;
026: import com.db4o.config.*;
027: import com.db4o.ext.*;
028: import com.db4o.foundation.*;
029: import com.db4o.foundation.io.*;
030: import com.db4o.internal.*;
031: import com.db4o.internal.btree.*;
032: import com.db4o.internal.classindex.*;
033:
034: /**
035: * defragments database files.
036: *
037: * <br><br>db4o structures storage inside database files as free and occupied slots, very
038: * much like a file system - and just like a file system it can be fragmented.<br><br>
039: *
040: * The simplest way to defragment a database file:<br><br>
041: *
042: * <code>Defragment.defrag("sample.yap");</code><br><br>
043: *
044: * This will move the file to "sample.yap.backup", then create a defragmented
045: * version of this file in the original position, using a temporary file
046: * "sample.yap.mapping". If the backup file already exists, this will throw an
047: * exception and no action will be taken.<br><br>
048: *
049: * For more detailed configuration of the defragmentation process, provide a
050: * DefragmentConfig instance:<br><br>
051: *
052: * <code>DefragmentConfig config=new DefragmentConfig("sample.yap","sample.bap",new BTreeIDMapping("sample.map"));<br>
053: * config.forceBackupDelete(true);<br>
054: * config.storedClassFilter(new AvailableClassFilter());<br>
055: * config.db4oConfig(db4oConfig);<br>
056: * Defragment.defrag(config);</code><br><br>
057: *
058: * This will move the file to "sample.bap", then create a defragmented version
059: * of this file in the original position, using a temporary file "sample.map" for BTree mapping.
060: * If the backup file already exists, it will be deleted. The defragmentation
061: * process will skip all classes that have instances stored within the yap file,
062: * but that are not available on the class path (through the current
063: * classloader). Custom db4o configuration options are read from the
064: * {@link com.db4o.config.Configuration Configuration} passed as db4oConfig.
065: *
066: * <strong>Note:</strong> For some specific, non-default configuration settings like
067: * UUID generation, etc., you <strong>must</strong> pass an appropriate db4o configuration,
068: * just like you'd use it within your application for normal database operation.
069: */
070: public class Defragment {
071:
072: /**
073: * Renames the file at the given original path to a backup file and then
074: * builds a defragmented version of the file in the original place.
075: *
076: * @param origPath
077: * The path to the file to be defragmented.
078: * @throws IOException
079: * if the original file cannot be moved to the backup location
080: */
081: public static void defrag(String origPath) throws IOException {
082: defrag(new DefragmentConfig(origPath), new NullListener());
083: }
084:
085: /**
086: * Renames the file at the given original path to the given backup file and
087: * then builds a defragmented version of the file in the original place.
088: *
089: * @param origPath
090: * The path to the file to be defragmented.
091: * @param backupPath
092: * The path to the backup file to be created.
093: * @throws IOException
094: * if the original file cannot be moved to the backup location
095: */
096: public static void defrag(String origPath, String backupPath)
097: throws IOException {
098: defrag(new DefragmentConfig(origPath, backupPath),
099: new NullListener());
100: }
101:
102: /**
103: * Renames the file at the configured original path to the configured backup
104: * path and then builds a defragmented version of the file in the original
105: * place.
106: *
107: * @param config
108: * The configuration for this defragmentation run.
109: * @throws IOException
110: * if the original file cannot be moved to the backup location
111: */
112: public static void defrag(DefragmentConfig config)
113: throws IOException {
114: defrag(config, new NullListener());
115: }
116:
117: /**
118: * Renames the file at the configured original path to the configured backup
119: * path and then builds a defragmented version of the file in the original
120: * place.
121: *
122: * @param config
123: * The configuration for this defragmentation run.
124: * @param listener
125: * A listener for status notifications during the defragmentation
126: * process.
127: * @throws IOException
128: * if the original file cannot be moved to the backup location
129: */
130: public static void defrag(DefragmentConfig config,
131: DefragmentListener listener) throws IOException {
132: File backupFile = new File(config.backupPath());
133: if (backupFile.exists()) {
134: if (!config.forceBackupDelete()) {
135: throw new IOException("Could not use '"
136: + config.backupPath()
137: + "' as backup path - file exists.");
138: }
139: backupFile.delete();
140: }
141: File4.rename(config.origPath(), config.backupPath());
142:
143: if (config.fileNeedsUpgrade()) {
144: upgradeFile(config);
145: }
146:
147: DefragContextImpl context = new DefragContextImpl(config,
148: listener);
149: int newClassCollectionID = 0;
150: int targetIdentityID = 0;
151: int targetUuidIndexID = 0;
152: try {
153: firstPass(context, config);
154: secondPass(context, config);
155: defragUnindexed(context);
156: newClassCollectionID = context.mappedID(context
157: .sourceClassCollectionID());
158: context.targetClassCollectionID(newClassCollectionID);
159: int sourceIdentityID = context
160: .databaseIdentityID(DefragContextImpl.SOURCEDB);
161: targetIdentityID = context.mappedID(sourceIdentityID, 0);
162: targetUuidIndexID = context.mappedID(context
163: .sourceUuidIndexID(), 0);
164: } catch (CorruptionException exc) {
165: exc.printStackTrace();
166: } finally {
167: context.close();
168: }
169: if (targetIdentityID > 0) {
170: setIdentity(config, targetIdentityID, targetUuidIndexID);
171: } else {
172: listener.notifyDefragmentInfo(new DefragmentInfo(
173: "No database identity found in original file."));
174: }
175: }
176:
177: private static void upgradeFile(DefragmentConfig config)
178: throws IOException {
179: File4.copy(config.backupPath(), config.tempPath());
180: Configuration db4oConfig = (Configuration) ((Config4Impl) config
181: .db4oConfig()).deepClone(null);
182: db4oConfig.allowVersionUpdates(true);
183: ObjectContainer db = Db4o.openFile(db4oConfig, config
184: .tempPath());
185: db.close();
186: }
187:
188: private static void defragUnindexed(DefragContextImpl context)
189: throws CorruptionException, IOException {
190: Iterator4 unindexedIDs = context.unindexedIDs();
191: while (unindexedIDs.moveNext()) {
192: final int origID = ((Integer) unindexedIDs.current())
193: .intValue();
194: BufferPair.processCopy(context, origID,
195: new SlotCopyHandler() {
196: public void processCopy(BufferPair readers)
197: throws CorruptionException {
198: ClassMetadata.defragObject(readers);
199: }
200:
201: }, true);
202: }
203: }
204:
205: private static void setIdentity(DefragmentConfig config,
206: int targetIdentityID, int targetUuidIndexID) {
207: LocalObjectContainer targetDB = (LocalObjectContainer) Db4o
208: .openFile(config.clonedDb4oConfig(), config.origPath());
209: try {
210: Db4oDatabase identity = (Db4oDatabase) targetDB
211: .getByID(targetIdentityID);
212: targetDB.setIdentity(identity);
213: targetDB.systemData().uuidIndexId(targetUuidIndexID);
214: } finally {
215: targetDB.close();
216: }
217: }
218:
219: private static void firstPass(DefragContextImpl context,
220: DefragmentConfig config) throws CorruptionException,
221: IOException {
222: // System.out.println("FIRST");
223: pass(context, config, new FirstPassCommand());
224: }
225:
226: private static void secondPass(final DefragContextImpl context,
227: DefragmentConfig config) throws CorruptionException,
228: IOException {
229: // System.out.println("SECOND");
230: pass(context, config, new SecondPassCommand(config
231: .objectCommitFrequency()));
232: }
233:
234: private static void pass(DefragContextImpl context,
235: DefragmentConfig config, PassCommand command)
236: throws CorruptionException, IOException {
237: command.processClassCollection(context);
238: StoredClass[] classes = context
239: .storedClasses(DefragContextImpl.SOURCEDB);
240: for (int classIdx = 0; classIdx < classes.length; classIdx++) {
241: ClassMetadata yapClass = (ClassMetadata) classes[classIdx];
242: if (!config.storedClassFilter().accept(yapClass)) {
243: continue;
244: }
245: processYapClass(context, yapClass, command);
246: command.flush(context);
247: if (config.objectCommitFrequency() > 0) {
248: context.targetCommit();
249: }
250: }
251: BTree uuidIndex = context.sourceUuidIndex();
252: if (uuidIndex != null) {
253: command.processBTree(context, uuidIndex);
254: }
255: command.flush(context);
256: context.targetCommit();
257: }
258:
259: // TODO order of class index/object slot processing is crucial:
260: // - object slots before field indices (object slots register addresses for
261: // use by string indices)
262: // - class index before object slots, otherwise phantom btree entries from
263: // deletions appear in the source class index?!?
264: // reproducable with SelectiveCascadingDeleteTestCase and ObjectSetTestCase
265: // - investigate.
266: private static void processYapClass(
267: final DefragContextImpl context,
268: final ClassMetadata curClass, final PassCommand command)
269: throws CorruptionException, IOException {
270: processClassIndex(context, curClass, command);
271: if (!parentHasIndex(curClass)) {
272: processObjectsForYapClass(context, curClass, command);
273: }
274: processYapClassAndFieldIndices(context, curClass, command);
275: }
276:
277: private static boolean parentHasIndex(ClassMetadata curClass) {
278: ClassMetadata parentClass = curClass.i_ancestor;
279: while (parentClass != null) {
280: if (parentClass.hasClassIndex()) {
281: return true;
282: }
283: parentClass = parentClass.i_ancestor;
284: }
285: return false;
286: }
287:
288: private static void processObjectsForYapClass(
289: final DefragContextImpl context,
290: final ClassMetadata curClass, final PassCommand command) {
291: context.traverseAll(curClass, new Visitor4() {
292: public void visit(Object obj) {
293: int id = ((Integer) obj).intValue();
294: try {
295: // FIXME bubble up exceptions
296: command.processObjectSlot(context, curClass, id);
297: } catch (CorruptionException e) {
298: e.printStackTrace();
299: } catch (IOException e) {
300: e.printStackTrace();
301: }
302: }
303: });
304: }
305:
306: private static void processYapClassAndFieldIndices(
307: final DefragContextImpl context,
308: final ClassMetadata curClass, final PassCommand command)
309: throws CorruptionException, IOException {
310: int sourceClassIndexID = 0;
311: int targetClassIndexID = 0;
312: if (curClass.hasClassIndex()) {
313: sourceClassIndexID = curClass.index().id();
314: targetClassIndexID = context.mappedID(sourceClassIndexID,
315: -1);
316: }
317: command.processClass(context, curClass, curClass.getID(),
318: targetClassIndexID);
319: }
320:
321: private static void processClassIndex(
322: final DefragContextImpl context,
323: final ClassMetadata curClass, final PassCommand command)
324: throws CorruptionException, IOException {
325: if (curClass.hasClassIndex()) {
326: BTreeClassIndexStrategy indexStrategy = (BTreeClassIndexStrategy) curClass
327: .index();
328: final BTree btree = indexStrategy.btree();
329: command.processBTree(context, btree);
330: }
331: }
332:
333: static class NullListener implements DefragmentListener {
334: public void notifyDefragmentInfo(DefragmentInfo info) {
335: }
336: }
337: }
|