|
12. TranslatorsIn the last chapter we have covered the alternative configurations db4o offers for object reinstantiation. What's left to see is how we can store objects of a class that can't be cleanly stored with either of these approaches.
12.1. An example classFor this example we'll be using a hypothetical LocalizedItemList class which binds together culture information with a list of items.
System.Globalization.CultureInfo is particularly interesting because it internally holds a native pointer to a system structure which in turn cannot be cleanly stored by db4o.
namespace com.db4o.f1.chapter6
{
using System.Globalization;
/// <summary>
/// A CultureInfo aware list of objects.
/// CultureInfo objects hold a native pointer to
/// a system structure.
/// </summary>
public class LocalizedItemList
{
CultureInfo _culture;
string[] _items;
public LocalizedItemList(CultureInfo culture, string[] items)
{
_culture = culture;
_items = items;
}
override public string ToString()
{
return string.Join(_culture.TextInfo.ListSeparator + " ", _items);
}
}
}
|
We'll be using this code to store and retrieve and instance of this class with different configuration settings:
public static void tryStoreAndRetrieve()
{
ObjectContainer db = Db4o.openFile(Util.YapFileName);
try
{
string[] champs = new string[] { "Ayrton Senna", "Nelson Piquet" };
LocalizedItemList LocalizedItemList = new LocalizedItemList(CultureInfo.CreateSpecificCulture("pt-BR"), champs);
System.Console.WriteLine("ORIGINAL: {0}", LocalizedItemList);
db.set(LocalizedItemList);
}
catch (Exception x)
{
System.Console.WriteLine(x);
return;
}
finally
{
db.close();
}
db = Db4o.openFile(Util.YapFileName);
try
{
ObjectSet result = db.get(typeof(LocalizedItemList));
while (result.hasNext())
{
LocalizedItemList LocalizedItemList = (LocalizedItemList)result.next();
System.Console.WriteLine("RETRIEVED: {0}", LocalizedItemList);
db.delete(LocalizedItemList);
}
}
finally
{
db.close();
}
} |
Let's verify that both approaches to object reinstantiation will fail for this class.
12.1.1. Using the constructor
[tryStoreWithCallConstructors]
Db4o.configure().exceptionsOnNotStorable(true);
Db4o.configure().objectClass(typeof(CultureInfo))
.callConstructor(true);
tryStoreAndRetrieve(); |
At storage time, db4o tests the only available constructor with null arguments and runs into a NullPointerException, so it refuses to accept our object.
(Note that this test only occurs when configured with exceptionsOnNotStorable - otherwise db4o will silently fail when trying to reinstantiate the object.)
12.1.2. Bypassing the constructor
[tryStoreWithoutCallConstructors]
Db4o.configure().objectClass(typeof(CultureInfo))
.callConstructor(false);
// trying to store objects that hold onto
// system resources can be pretty nasty
// uncomment the following line to see
// how nasty it can be
//tryStoreAndRetrieve(); |
This still does not work for our case because the native pointer will definetely be invalid. In fact this example crashes the Common Language Runtime.
12.2. The Translator APISo how do we get our object into the database, now that everything seems to fail? Db4o provides a way to specify a custom way of storing and retrieving objects through the ObjectTranslator and ObjectConstructor interfaces.
12.2.1. ObjectTranslatorThe ObjectTranslator API looks like this:
public Object onStore(ObjectContainer container,
Object applicationObject);
public void onActivate(ObjectContainer container,
Object applicationObject,
Object storedObject);
public Class storedClass (); |
The usage is quite simple: When a translator is configured for a class, db4o will call its onStore method with a reference to the database and the instance to be stored as a parameter and will store the object returned. This object's type has to be primitive from a db4o point of view and it has to match the type specification returned by storedClass().
On retrieval, db4o will create a blank object of the target class (using the configured instantiation method) and then pass it on to onActivate() along with the stored object to be set up accordingly.
12.2.2. ObjectConstructorHowever, this will only work if the application object's class provides some way to recreate its state from the information contained in the stored object, which is not the case for CultureInfo.
For these cases db4o provides an extension to the ObjectTranslator interface, ObjectConstructor, which declares one additional method:
public Object onInstantiate(ObjectContainer container,
Object storedObject); |
If db4o detects a configured translator to be an ObjectConstructor implementation, it will pass the stored class instance to the onInstantiate() method and use the result as a blank application object to be processed by onActivate().
Note that, while in general configured translators are applied to subclasses, too, ObjectConstructor application object instantiation will not be used for subclasses (which wouldn't make much sense, anyway), so ObjectConstructors have to be configured for the concrete classes.
12.3. A translator implementationTo translate CultureInfo instances, we will store only their name since this is enough to recreate them later. Note that we don't have to do any work in onActivate(), since object reinstantiation is already fully completed in onInstantiate().
namespace com.db4o.f1.chapter6
{
using System.Globalization;
using com.db4o;
using com.db4o.config;
public class CultureInfoTranslator : ObjectConstructor
{
public object onStore(ObjectContainer container, object applicationObject)
{
System.Console.WriteLine("onStore for {0}", applicationObject);
return ((CultureInfo)applicationObject).Name;
}
public object onInstantiate(ObjectContainer container, object storedObject)
{
System.Console.WriteLine("onInstantiate for {0}", storedObject);
string name = (string)storedObject;
return CultureInfo.CreateSpecificCulture(name);
}
public void onActivate(ObjectContainer container, object applicationObject, object storedObject)
{
System.Console.WriteLine("onActivate for {0}/{1}", applicationObject, storedObject);
}
public j4o.lang.Class storedClass()
{
return j4o.lang.Class.getClassForType(typeof(string));
}
}
}
|
Let's try it out:
[storeWithTranslator]
Db4o.configure().objectClass(typeof(CultureInfo))
.translate(new CultureInfoTranslator());
tryStoreAndRetrieve();
Db4o.configure().objectClass(typeof(CultureInfo))
.translate(null); |
OUTPUT: ORIGINAL: 42/Test: 4
onStore for 42/Test: 4
onInstantiate for [Ljava.lang.Object;@654339ed
onActivate for 42/Test: 4 / [Ljava.lang.Object;@18c14dd1
RETRIEVED: 42/Test: 4
|
|