Converts an old version of an object value to conform to the current class
or field definition.
The
Conversion interface is implemented by the user. A
Conversion instance is passed to the
Converter.Converter constructor.
The
Conversion interface extends
Serializable and the
Conversion instance is serialized for storage using standard Java
serialization. Normally, the
Conversion class should only have
transient fields that are initialized in the
Conversion.initialize method.
While non-transient fields are allowed, care must be taken to only include
fields that are serializable and will not pull in large amounts of data.
When a class conversion is specified, two special considerations
apply:
- A class conversion is only applied when to instances of that class. The
conversion will not be applied when the class when it appears as a
superclass of the instance's class. In this case, a conversion for the
instance's class must also be specified.
- Although field renaming (as well as all other changes) is handled by the
conversion method, a field Renamer is still needed when a secondary key
field is renamed and field Deleter is still needed when a secondary key
field is deleted. This is necessary for evolution of the metadata;
specifically, if the key name changes the database must be renamed and if
the key field is deleted the secondary database must be deleted.
The
Conversion class must implement the standard equals method.
See
Conversion.equals for more information.
Conversions of simple types are generally simple. For example, a
String field that contains only integer values can be easily converted to
an
int field:
// The old class. Version 0 is implied.
//
class Address {
String zipCode;
...
}
// The new class. A new version number must be assigned.
//
class Address {
int zipCode;
...
}
// The conversion class.
//
class MyConversion1 implements Conversion {
public void initialize(EntityModel model) {
// No initialization needed.
}
public Object convert(Object fromValue) {
return Integer.valueOf((String) fromValue);
}
@Override public boolean equals(Object o) {
return o instanceof MyConversion1;
}
}
// Create a field converter mutation.
//
Converter converter = new Converter(Address.class.getName(), 0,
"zipCode", new MyConversion1());
// Configure the converter as described
Mutations here .
A conversion may perform arbitrary transformations on an object. For
example, a conversion may transform a single String address field into an
Address object containing four fields for street, city, state and zip
code.
// The old class. Version 0 is implied.
//
class Person {
String address;
...
}
// The new class. A new version number must be assigned.
//
class Person {
Address address;
...
}
// The new address class.
//
class Address {
String street;
String city;
String state;
int zipCode;
...
}
class MyConversion2 implements Conversion {
private transient RawType addressType;
public void initialize(EntityModel model) {
addressType = model.getRawType(Address.class.getName());
}
public Object convert(Object fromValue) {
// Parse the old address and populate the new address fields
//
String oldAddress = (String) fromValue;
addressValues.put("street", parseStreet(oldAddress));
addressValues.put("city", parseCity(oldAddress));
addressValues.put("state", parseState(oldAddress));
addressValues.put("zipCode", parseZipCode(oldAddress));
// Return new raw Address object
//
return new RawObject(addressType, addressValues, null);
}
@Override public boolean equals(Object o) {
return o instanceof MyConversion2;
}
private String parseStreet(String oldAddress) { ... }
private String parseCity(String oldAddress) { ... }
private String parseState(String oldAddress) { ... }
private Integer parseZipCode(String oldAddress) { ... }
}
// Create a field converter mutation.
//
Converter converter = new Converter(Person.class.getName(), 0,
"address", new MyConversion2());
// Configure the converter as described
Mutations here .
Note that when a conversion returns a
RawObject , it must return
it with a
RawType that is current as defined by the current class
definitions. The proper types can be obtained from the
EntityModel in the conversion's
Conversion.initialize initialize method.
A variation on the example above is where several fields in a class
(street, city, state and zipCode) are converted to a single field (address).
In this case a class converter rather than a field converter is used.
// The old class. Version 0 is implied.
//
class Person {
String street;
String city;
String state;
int zipCode;
...
}
// The new class. A new version number must be assigned.
//
class Person {
Address address;
...
}
// The new address class.
//
class Address {
String street;
String city;
String state;
int zipCode;
...
}
class MyConversion3 implements Conversion {
private transient RawType newPersonType;
private transient RawType addressType;
public void initialize(EntityModel model) {
newPersonType = model.getRawType(Person.class.getName());
addressType = model.getRawType(Address.class.getName());
}
public Object convert(Object fromValue) {
// Get field value maps for old and new objects.
//
RawObject person = (RawObject) fromValue;
RawObject address = new RawObject(addressType, addressValues, null);
// Remove the old address fields and insert the new one.
//
addressValues.put("street", personValues.remove("street"));
addressValues.put("city", personValues.remove("city"));
addressValues.put("state", personValues.remove("state"));
addressValues.put("zipCode", personValues.remove("zipCode"));
personValues.put("address", address);
return new RawObject(newPersonType, personValues, person.getSuper());
}
@Override public boolean equals(Object o) {
return o instanceof MyConversion3;
}
}
// Create a class converter mutation.
//
Converter converter = new Converter(Person.class.getName(), 0,
new MyConversion3());
// Configure the converter as described
Mutations here .
A conversion can also handle changes to class hierarchies. For example,
if a "name" field originally declared in class A is moved to its superclass
B, a conversion can move the field value accordingly:
// The old classes. Version 0 is implied.
//
class A extends B {
String name;
...
}
abstract class B {
...
}
// The new classes. A new version number must be assigned.
//
class A extends B {
...
}
abstract class B {
String name;
...
}
class MyConversion4 implements Conversion {
private transient RawType newAType;
private transient RawType newBType;
public void initialize(EntityModel model) {
newAType = model.getRawType(A.class.getName());
newBType = model.getRawType(B.class.getName());
}
public Object convert(Object fromValue) {
RawObject oldA = (RawObject) fromValue;
RawObject oldB = oldA.getSuper();
bValues.put("name", aValues.remove("name"));
RawObject newB = new RawObject(newBType, bValues, oldB.getSuper());
RawObject newA = new RawObject(newAType, aValues, newB);
return newA;
}
@Override public boolean equals(Object o) {
return o instanceof MyConversion4;
}
}
// Create a class converter mutation.
//
Converter converter = new Converter(A.class.getName(), 0,
new MyConversion4());
// Configure the converter as described
Mutations here .
A conversion may return an instance of a different class entirely, as
long as it conforms to current class definitions and is the type expected
in the given context (a subtype of the old type, or a type compatible with
the new field type). For example, a field that is used to discriminate
between two types of objects could be removed and replaced by two new
subclasses:
// The old class. Version 0 is implied.
//
class Pet {
boolean isCatNotDog;
...
}
// The new classes. A new version number must be assigned to the Pet class.
//
class Pet {
...
}
class Cat extends Pet {
...
}
class Dog extends Pet {
...
}
class MyConversion5 implements Conversion {
private transient RawType newPetType;
private transient RawType dogType;
private transient RawType catType;
public void initialize(EntityModel model) {
newPetType = model.getRawType(Pet.class.getName());
dogType = model.getRawType(Dog.class.getName());
catType = model.getRawType(Cat.class.getName());
}
public Object convert(Object fromValue) {
RawObject pet = (RawObject) fromValue;
Boolean isCat = (Boolean) petValues.remove("isCatNotDog");
RawObject newPet = new RawObject(newPetType, petValues,
pet.getSuper());
RawType newSubType = isCat ? catType : dogType;
return new RawObject(newSubType, Collections.emptyMap(), newPet);
}
@Override public boolean equals(Object o) {
return o instanceof MyConversion5;
}
}
// Create a class converter mutation.
//
Converter converter = new Converter(Pet.class.getName(), 0,
new MyConversion5());
// Configure the converter as described
Mutations here .
The primary limitation of a conversion is that it may access at most a
single entity instance at one time. Conversions involving multiple entities
at once may be made by performing a store conversion.
See Also: com.sleepycat.persist.evolve See Also: Class Evolution author: Mark Hayes |