old class. Version 0 is implied. // {@literal @Persistent}class Address { String zipCode; ... } // The new class. A new version number must be assigned. // {@literal @Persistent(version=1)}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); } {@code @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 {@link 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. // {@literal @Entity}class Person { String address; ... } // The new class. A new version number must be assigned. // {@literal @Entity(version=1)}class Person { Address address; ... } // The new address class. // {@literal @Persistent}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; {@literal Map addressValues = new HashMap();}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); } {@code @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 {@link Mutations here}.
Note that when a conversion returns a {@link RawObject}, it must return it with a {@link RawType} that is current as defined by the current classdefinitions. The proper types can be obtained from the {@link EntityModel}in the conversion's {@link #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. // {@literal @Entity}class Person { String street; String city; String state; int zipCode; ... } // The new class. A new version number must be assigned. // {@literal @Entity(version=1)}class Person { Address address; ... } // The new address class. // {@literal @Persistent}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; {@literal Map personValues = person.getValues();}{@literal Map addressValues = new HashMap();}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()); } {@code @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 {@link 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. // {@literal @Persistent}class A extends B { String name; ... } {@literal @Persistent}abstract class B { ... } // The new classes. A new version number must be assigned. // {@literal @Persistent(version=1)}class A extends B { ... } {@literal @Persistent(version=1)}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(); {@literal Map aValues = oldA.getValues();}{@literal Map bValues = oldB.getValues();}bValues.put("name", aValues.remove("name")); RawObject newB = new RawObject(newBType, bValues, oldB.getSuper()); RawObject newA = new RawObject(newAType, aValues, newB); return newA; } {@code @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 {@link 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. // {@literal @Persistent}class Pet { boolean isCatNotDog; ... } // The new classes. A new version number must be assigned to the Pet class. // {@literal @Persistent(version=1)}class Pet { ... } {@literal @Persistent}class Cat extends Pet { ... } {@literal @Persistent}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; {@literal Map petValues = pet.getValues();}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); } {@code @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 {@link 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 com.sleepycat.persist.evolve Class Evolution
@author Mark Hayes