/* Copyright (C) 2004 - 2006 db4objects Inc. http://www.db4o.com
This file is part of the db4o open source object database.
db4o is free software; you can redistribute it and/or modify it under
the terms of version 2 of the GNU General Public License as published
by the Free Software Foundation and as clarified by db4objects' GPL
interpretation policy, available at
http://www.db4o.com/about/company/legalpolicies/gplinterpretation/
Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street,
Suite 350, San Mateo, CA 94403, USA.
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, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
namespace Db4oTools
{
using System;
using j4o.io;
using j4o.lang;
using j4o.lang.reflect;
using com.db4o;
using com.db4o.ext;
using com.db4o.types;
/**
* defragments a database file.
*
This class is not part of db4o.jar. It is delivered as
* sourcecode in the path ../com/db4o/tools/
* Prerequites:
* - The database file may not be in use.
* - All stored classes need to be available.
* - If you use yor own special Db4o translators, they need to be
* installed before starting Defragment.
*
* Performed tasks:
* - Free filespace is removed.
* - Deleted IDs are removed.
* - Unavailable classes are removed.
* - Unavailable class members are removed.
* - Class indices are restored.
* - Previous rename tasks are removed.
*
* Backup:
* Defragment creates a backup file with the name [filename].bak.
* If a file with this name is already present, Defragment will not run
* for safety reasons.
*
* Recommendations:
* - Keep the backup copy of your database file.
* - Always back up your class files with your database files also.
* You will need them to restore the full data of all objects from old
* database file versions.
* - Scan the output log for "Class not available" messages.
*
* You may also run this task programmatically on a scheduled basis. In this
* case note that Defragment modifies db4o configuration
* parameters. You may have to restore them for your application. See the
* private methods Defragment#ConfigureDb4o() and Db4o#RestoreConfiguration()
* in the sourcecode of com.db4o.tools.Defragment.cs for the exact changed
* parameters that may need to be restored.
*/
public class Defragment
{
static readonly j4o.lang.Class ObjectClass = j4o.lang.Class.GetClassForType(typeof(object));
/**
* the main method is the only entry point
*/
public Defragment()
: base()
{
}
/**
* the main method that runs Defragment.
* @param String[] a String array of length 1, with the name of
* the database file as element 0.
*/
public static void Main(String[] args)
{
if (args != null && args.Length > 0)
{
bool forceBackupDelete1 = args.Length > 1 && "!".Equals(args[1]);
new Defragment().Run(args[0], forceBackupDelete1);
}
else
{
Console.WriteLine("Usage: java com.db4o.tools.Defragment ");
}
}
/**
* programmatic interface to run Defragment with a forced delete of
* a possible old Defragment backup.
* This method is supplied for regression tests only. It is not
* recommended to be used by application programmers.
* @param filename the database file.
* @param forceBackupDelete forces deleting an old backup.
* Not recommended.
*/
public void Run(String filename, bool forceBackupDelete)
{
File file = new File(filename);
if (file.Exists())
{
bool canRun = true;
ExtFile backupTest = new ExtFile(file.GetAbsolutePath() + ".bak");
if (backupTest.Exists())
{
if (forceBackupDelete)
{
backupTest.Delete();
}
else
{
canRun = false;
Console.WriteLine("A backup file with the name ");
Console.WriteLine("\'" + backupTest.GetAbsolutePath() + "\'");
Console.WriteLine("already exists.");
Console.WriteLine("Remove this file before calling \'Defragment\'.");
}
}
if (canRun)
{
file.RenameTo(backupTest);
try
{
ConfigureDb4o();
ObjectContainer readFrom = Db4o.OpenFile(backupTest.GetAbsolutePath());
ObjectContainer writeTo = Db4o.OpenFile(file.GetAbsolutePath());
writeTo.Ext().MigrateFrom(readFrom);
Migrate(readFrom, writeTo);
readFrom.Close();
writeTo.Close();
Console.WriteLine("Defragment operation completed successfully.");
}
catch (Exception e)
{
Console.WriteLine("Defragment operation failed.");
j4o.lang.JavaSystem.PrintStackTrace(e);
try
{
new File(filename).Delete();
backupTest.Copy(filename);
}
catch (Exception ex)
{
Console.WriteLine("Restore failed.");
Console.WriteLine("Please use the backup file:");
Console.WriteLine("\'" + backupTest.GetAbsolutePath() + "\'");
return;
}
Console.WriteLine("The original file was restored.");
try
{
new File(backupTest.GetAbsolutePath()).Delete();
}
catch (Exception ignored)
{
}
}
finally
{
RestoreConfiguration();
}
}
}
else
{
Console.WriteLine("File \'" + file.GetAbsolutePath() + "\' does not exist.");
}
}
private void ConfigureDb4o()
{
Db4o.Configure().ActivationDepth(0);
Db4o.Configure().Callbacks(false);
Db4o.Configure().ClassActivationDepthConfigurable(false);
Db4o.Configure().WeakReferences(false);
}
private void RestoreConfiguration()
{
Db4o.Configure().ActivationDepth(5);
Db4o.Configure().Callbacks(true);
Db4o.Configure().ClassActivationDepthConfigurable(true);
Db4o.Configure().WeakReferences(true);
}
private void Migrate(ObjectContainer origin, ObjectContainer destination)
{
StoredClass[] classes = origin.Ext().StoredClasses();
FilterAbstractSecondAndNotFoundClasses(classes);
FilterSubclasses(classes);
MigrateClasses(origin, destination, classes);
}
private static void MigrateClasses(ObjectContainer origin, ObjectContainer destination, StoredClass[] classes)
{
for (int i = 0; i < classes.Length; i++)
{
if (classes[i] != null)
{
long[] ids = classes[i].GetIDs();
origin.Ext().Purge();
destination.Commit();
destination.Ext().Purge();
for (int j = 0; j < ids.Length; j++)
{
Object obj = origin.Ext().GetByID(ids[j]);
origin.Activate(obj, 1);
origin.Deactivate(obj, 2);
origin.Activate(obj, 3);
destination.Set(obj);
origin.Deactivate(obj, 1);
destination.Deactivate(obj, 1);
}
}
}
}
///
/// Remove subclasses from the list since objects from subclasses will be
/// returned by superclass.GetIds()
///
///
private static void FilterSubclasses(StoredClass[] classes)
{
for (int i = 0; i < classes.Length; i++)
{
if (classes[i] == null)
{
continue;
}
Class javaClass = Class.ForName(classes[i].GetName());
if (IsSubclass(classes, javaClass))
{
classes[i] = null;
}
}
}
private static bool IsSubclass(StoredClass[] classes, Class candidate)
{
for (int j = 0; j < classes.Length; j++)
{
if (classes[j] != null)
{
Class superClass1 = Class.ForName(classes[j].GetName());
if (candidate != superClass1 && superClass1.IsAssignableFrom(candidate))
{
return true;
}
}
}
return false;
}
private static void FilterAbstractSecondAndNotFoundClasses(StoredClass[] classes)
{
for (int i = 0; i < classes.Length; i++)
{
try
{
Class javaClass = Class.ForName(classes[i].GetName());
if (javaClass == null
|| javaClass == ObjectClass
|| IsSecondClass(javaClass)
|| Modifier.IsAbstract(javaClass.GetModifiers()))
{
classes[i] = null;
}
}
catch (ClassNotFoundException e)
{
classes[i] = null;
}
}
}
private static bool IsSecondClass(Class javaClass)
{
return Class.GetClassForType(typeof(SecondClass)).IsAssignableFrom(javaClass);
}
private class ExtFile : File
{
public ExtFile(String path)
: base(path)
{
}
public ExtFile Copy(String toPath)
{
try
{
{
new ExtFile(toPath).Mkdirs();
new ExtFile(toPath).Delete();
int bufferSize1 = 64000;
RandomAccessFile rafIn = new RandomAccessFile(GetAbsolutePath(), "r");
RandomAccessFile rafOut = new RandomAccessFile(toPath, "rw");
long len = rafIn.Length();
byte[] bytes = new byte[bufferSize1];
while (len > 0)
{
len -= bufferSize1;
if (len < 0)
{
bytes = new byte[(int)(len + bufferSize1)];
}
rafIn.Read(bytes);
rafOut.Write(bytes);
}
rafIn.Close();
rafOut.Close();
return new ExtFile(toPath);
}
}
catch (Exception e)
{
{
j4o.lang.JavaSystem.PrintStackTrace(e);
throw e;
}
}
}
}
}
}