| /*- |
| * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved. |
| * |
| * This file was distributed by Oracle as part of a version of Oracle Berkeley |
| * DB Java Edition made available at: |
| * |
| * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html |
| * |
| * Please see the LICENSE file included in the top-level directory of the |
| * appropriate version of Oracle Berkeley DB Java Edition for a copy of the |
| * license and additional information. |
| */ |
| |
| package com.sleepycat.persist.evolve; |
| |
| import java.io.Serializable; |
| |
| import com.sleepycat.persist.model.EntityModel; |
| import com.sleepycat.persist.raw.RawObject; |
| import com.sleepycat.persist.raw.RawType; |
| |
| /** |
| * Converts an old version of an object value to conform to the current class |
| * or field definition. |
| * |
| * <p>The {@code Conversion} interface is implemented by the user. A |
| * {@code Conversion} instance is passed to the {@link Converter#Converter} |
| * constructor.</p> |
| * |
| * <p>The {@code Conversion} interface extends {@link Serializable} and the |
| * {@code Conversion} instance is serialized for storage using standard Java |
| * serialization. Normally, the {@code Conversion} class should only have |
| * transient fields that are initialized in the {@link #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.</p> |
| * |
| * <p>When a class conversion is specified, two special considerations |
| * apply:</p> |
| * <ol> |
| * <li>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.</li> |
| * <li>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.</li> |
| * </ol> |
| * |
| * <p>The {@code Conversion} class must implement the standard equals method. |
| * See {@link #equals} for more information.</p> |
| * |
| * <p>Conversions of simple types are generally simple. For example, a {@code |
| * String} field that contains only integer values can be easily converted to |
| * an {@code int} field:</p> |
| * <pre class="code"> |
| * // The 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}.</pre> |
| * |
| * <p>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.</p> |
| * <pre class="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<String, Object> addressValues = new HashMap<String, Object>();} |
| * 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}.</pre> |
| * |
| * <p>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 class |
| * definitions. The proper types can be obtained from the {@link EntityModel} |
| * in the conversion's {@link #initialize initialize} method.</p> |
| * |
| * <p>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.</p> |
| * |
| * <pre class="code"> |
| * // 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<String, Object> personValues = person.getValues();} |
| * {@literal Map<String, Object> addressValues = new HashMap<String, Object>();} |
| * 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}.</pre> |
| * |
| * |
| * <p>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:</p> |
| * |
| * <pre class="code"> |
| * // 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<String, Object> aValues = oldA.getValues();} |
| * {@literal Map<String, Object> 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}.</pre> |
| * |
| * <p>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:</p> <pre class="code"> |
| * // 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<String, Object> 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}.</pre> |
| * |
| * <p>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 <a |
| * href="package-summary.html#storeConversion">store conversion</a>.</p> |
| * |
| * @see com.sleepycat.persist.evolve Class Evolution |
| * @author Mark Hayes |
| */ |
| public interface Conversion extends Serializable { |
| |
| /** |
| * Initializes the conversion, allowing it to obtain raw type information |
| * from the entity model. |
| * |
| * @param model the EntityModel. |
| */ |
| void initialize(EntityModel model); |
| |
| /** |
| * Converts an old version of an object value to conform to the current |
| * class or field definition. |
| * |
| * <p>If a {@link RuntimeException} is thrown by this method, it will be |
| * thrown to the original caller. Similarly, a {@link |
| * IllegalArgumentException} will be thrown to the original caller if the |
| * object returned by this method does not conform to current class |
| * definitions.</p> |
| * |
| * <p>The class of the input and output object may be one of the simple |
| * types or {@link RawObject}. For primitive types, the primitive wrapper |
| * class is used.</p> |
| * |
| * @param fromValue the object value being converted. The type of this |
| * value is defined by the old class version that is being converted. |
| * |
| * @return the converted object. The type of this value must conform to |
| * a current class definition. If this is a class conversion, it must |
| * be the current version of the class. If this is a field conversion, it |
| * must be of a type compatible with the current declared type of the |
| * field. |
| */ |
| Object convert(Object fromValue); |
| |
| /** |
| * The standard {@code equals} method that must be implemented by |
| * conversion class. |
| * |
| * <p>When mutations are specified when opening a store, the specified and |
| * previously stored mutations are compared for equality. If they are |
| * equal, there is no need to replace the existing mutations in the stored |
| * catalog. To accurately determine equality, the conversion class must |
| * implement the {@code equals} method.</p> |
| * |
| * <p>If the {@code equals} method is not explicitly implemented by the |
| * conversion class or a superclass other than {@code Object}, {@code |
| * IllegalArgumentException} will be thrown when the store is opened.</p> |
| * |
| * <p>Normally whenever {@code equals} is implemented the {@code hashCode} |
| * method should also be implemented to support hash sets and maps. |
| * However, hash sets and maps containing <code>Conversion</code> objects |
| * are not used by the DPL and therefore the DPL does not require |
| * {@code hashCode} to be implemented.</p> |
| */ |
| boolean equals(Object other); |
| } |