001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
005: *
006: * $Id: Conversion.java,v 1.9.2.3 2008/01/07 15:14:19 cwl Exp $
007: */
008:
009: package com.sleepycat.persist.evolve;
010:
011: import java.io.Serializable;
012:
013: import com.sleepycat.persist.model.EntityModel;
014: import com.sleepycat.persist.raw.RawObject;
015: import com.sleepycat.persist.raw.RawType;
016:
017: /**
018: * Converts an old version of an object value to conform to the current class
019: * or field definition.
020: *
021: * <p>The {@code Conversion} interface is implemented by the user. A
022: * {@code Conversion} instance is passed to the {@link Converter#Converter}
023: * constructor.</p>
024: *
025: * <p>The {@code Conversion} interface extends {@link Serializable} and the
026: * {@code Conversion} instance is serialized for storage using standard Java
027: * serialization. Normally, the {@code Conversion} class should only have
028: * transient fields that are initialized in the {@link #initialize} method.
029: * While non-transient fields are allowed, care must be taken to only include
030: * fields that are serializable and will not pull in large amounts of data.</p>
031: *
032: * <p>When a class conversion is specified, two special considerations
033: * apply:</p>
034: * <ol>
035: * <li>A class conversion is only applied when to instances of that class. The
036: * conversion will not be applied when the class when it appears as a
037: * superclass of the instance's class. In this case, a conversion for the
038: * instance's class must also be specified.</li>
039: * <li>Although field renaming (as well as all other changes) is handled by the
040: * conversion method, a field Renamer is still needed when a secondary key
041: * field is renamed and field Deleter is still needed when a secondary key
042: * field is deleted. This is necessary for evolution of the metadata;
043: * specifically, if the key name changes the database must be renamed and if
044: * the key field is deleted the secondary database must be deleted.</li>
045: * </ol>
046: *
047: * <p>The {@code Conversion} class must implement the standard equals method.
048: * See {@link #equals} for more information.</p>
049: *
050: * <p>Conversions of simple types are generally simple. For example, a {@code
051: * String} field that contains only integer values can be easily converted to
052: * an {@code int} field:</p>
053: * <pre class="code">
054: * // The old class. Version 0 is implied.
055: * //
056: * {@literal @Persistent}
057: * class Address {
058: * String zipCode;
059: * ...
060: * }
061: *
062: * // The new class. A new version number must be assigned.
063: * //
064: * {@literal @Persistent(version=1)}
065: * class Address {
066: * int zipCode;
067: * ...
068: * }
069: *
070: * // The conversion class.
071: * //
072: * class MyConversion1 implements Conversion {
073: *
074: * public void initialize(EntityModel model) {
075: * // No initialization needed.
076: * }
077: *
078: * public Object convert(Object fromValue) {
079: * return Integer.valueOf((String) fromValue);
080: * }
081: *
082: * {@code @Override}
083: * public boolean equals(Object o) {
084: * return o instanceof MyConversion1;
085: * }
086: * }
087: *
088: * // Create a field converter mutation.
089: * //
090: * Converter converter = new Converter(Address.class.getName(), 0,
091: * "zipCode", new MyConversion1());
092: *
093: * // Configure the converter as described {@link Mutations here}.</pre>
094: *
095: * <p>A conversion may perform arbitrary transformations on an object. For
096: * example, a conversion may transform a single String address field into an
097: * Address object containing four fields for street, city, state and zip
098: * code.</p>
099: * <pre class="code">
100: * // The old class. Version 0 is implied.
101: * //
102: * {@literal @Entity}
103: * class Person {
104: * String address;
105: * ...
106: * }
107: *
108: * // The new class. A new version number must be assigned.
109: * //
110: * {@literal @Entity(version=1)}
111: * class Person {
112: * Address address;
113: * ...
114: * }
115: *
116: * // The new address class.
117: * //
118: * {@literal @Persistent}
119: * class Address {
120: * String street;
121: * String city;
122: * String state;
123: * int zipCode;
124: * ...
125: * }
126: *
127: * class MyConversion2 implements Conversion {
128: * private transient RawType addressType;
129: *
130: * public void initialize(EntityModel model) {
131: * addressType = model.getRawType(Address.class.getName());
132: * }
133: *
134: * public Object convert(Object fromValue) {
135: *
136: * // Parse the old address and populate the new address fields
137: * //
138: * String oldAddress = (String) fromValue;
139: * {@literal Map<String,Object> addressValues = new HashMap<String,Object>();}
140: * addressValues.put("street", parseStreet(oldAddress));
141: * addressValues.put("city", parseCity(oldAddress));
142: * addressValues.put("state", parseState(oldAddress));
143: * addressValues.put("zipCode", parseZipCode(oldAddress));
144: *
145: * // Return new raw Address object
146: * //
147: * return new RawObject(addressType, addressValues, null);
148: * }
149: *
150: * {@code @Override}
151: * public boolean equals(Object o) {
152: * return o instanceof MyConversion2;
153: * }
154: *
155: * private String parseStreet(String oldAddress) { ... }
156: * private String parseCity(String oldAddress) { ... }
157: * private String parseState(String oldAddress) { ... }
158: * private Integer parseZipCode(String oldAddress) { ... }
159: * }
160: *
161: * // Create a field converter mutation.
162: * //
163: * Converter converter = new Converter(Person.class.getName(), 0,
164: * "address", new MyConversion2());
165: *
166: * // Configure the converter as described {@link Mutations here}.</pre>
167: *
168: * <p>Note that when a conversion returns a {@link RawObject}, it must return
169: * it with a {@link RawType} that is current as defined by the current class
170: * definitions. The proper types can be obtained from the {@link EntityModel}
171: * in the conversion's {@link #initialize initialize} method.</p>
172: *
173: * <p>A variation on the example above is where several fields in a class
174: * (street, city, state and zipCode) are converted to a single field (address).
175: * In this case a class converter rather than a field converter is used.</p>
176: *
177: * <pre class="code">
178: * // The old class. Version 0 is implied.
179: * //
180: * {@literal @Entity}
181: * class Person {
182: * String street;
183: * String city;
184: * String state;
185: * int zipCode;
186: * ...
187: * }
188: *
189: * // The new class. A new version number must be assigned.
190: * //
191: * {@literal @Entity(version=1)}
192: * class Person {
193: * Address address;
194: * ...
195: * }
196: *
197: * // The new address class.
198: * //
199: * {@literal @Persistent}
200: * class Address {
201: * String street;
202: * String city;
203: * String state;
204: * int zipCode;
205: * ...
206: * }
207: *
208: * class MyConversion3 implements Conversion {
209: * private transient RawType newPersonType;
210: * private transient RawType addressType;
211: *
212: * public void initialize(EntityModel model) {
213: * newPersonType = model.getRawType(Person.class.getName());
214: * addressType = model.getRawType(Address.class.getName());
215: * }
216: *
217: * public Object convert(Object fromValue) {
218: *
219: * // Get field value maps for old and new objects.
220: * //
221: * RawObject person = (RawObject) fromValue;
222: * {@literal Map<String,Object> personValues = person.getValues();}
223: * {@literal Map<String,Object> addressValues = new HashMap<String,Object>();}
224: * RawObject address = new RawObject(addressType, addressValues, null);
225: *
226: * // Remove the old address fields and insert the new one.
227: * //
228: * addressValues.put("street", personValues.remove("street"));
229: * addressValues.put("city", personValues.remove("city"));
230: * addressValues.put("state", personValues.remove("state"));
231: * addressValues.put("zipCode", personValues.remove("zipCode"));
232: * personValues.put("address", address);
233: *
234: * return new RawObject(newPersonType, personValues, person.getSuper());
235: * }
236: *
237: * {@code @Override}
238: * public boolean equals(Object o) {
239: * return o instanceof MyConversion3;
240: * }
241: * }
242: *
243: * // Create a class converter mutation.
244: * //
245: * Converter converter = new Converter(Person.class.getName(), 0,
246: * new MyConversion3());
247: *
248: * // Configure the converter as described {@link Mutations here}.</pre>
249: *
250: *
251: * <p>A conversion can also handle changes to class hierarchies. For example,
252: * if a "name" field originally declared in class A is moved to its superclass
253: * B, a conversion can move the field value accordingly:</p>
254: *
255: * <pre class="code">
256: * // The old classes. Version 0 is implied.
257: * //
258: * {@literal @Persistent}
259: * class A extends B {
260: * String name;
261: * ...
262: * }
263: * {@literal @Persistent}
264: * abstract class B {
265: * ...
266: * }
267: *
268: * // The new classes. A new version number must be assigned.
269: * //
270: * {@literal @Persistent(version=1)}
271: * class A extends B {
272: * ...
273: * }
274: * {@literal @Persistent(version=1)}
275: * abstract class B {
276: * String name;
277: * ...
278: * }
279: *
280: * class MyConversion4 implements Conversion {
281: * private transient RawType newAType;
282: * private transient RawType newBType;
283: *
284: * public void initialize(EntityModel model) {
285: * newAType = model.getRawType(A.class.getName());
286: * newBType = model.getRawType(B.class.getName());
287: * }
288: *
289: * public Object convert(Object fromValue) {
290: * RawObject oldA = (RawObject) fromValue;
291: * RawObject oldB = oldA.getSuper();
292: * {@literal Map<String,Object> aValues = oldA.getValues();}
293: * {@literal Map<String,Object> bValues = oldB.getValues();}
294: * bValues.put("name", aValues.remove("name"));
295: * RawObject newB = new RawObject(newBType, bValues, oldB.getSuper());
296: * RawObject newA = new RawObject(newAType, aValues, newB);
297: * return newA;
298: * }
299: *
300: * {@code @Override}
301: * public boolean equals(Object o) {
302: * return o instanceof MyConversion4;
303: * }
304: * }
305: *
306: * // Create a class converter mutation.
307: * //
308: * Converter converter = new Converter(A.class.getName(), 0,
309: * new MyConversion4());
310: *
311: * // Configure the converter as described {@link Mutations here}.</pre>
312: *
313: * <p>A conversion may return an instance of a different class entirely, as
314: * long as it conforms to current class definitions and is the type expected
315: * in the given context (a subtype of the old type, or a type compatible with
316: * the new field type). For example, a field that is used to discriminate
317: * between two types of objects could be removed and replaced by two new
318: * subclasses:</p> <pre class="code">
319: * // The old class. Version 0 is implied.
320: * //
321: * {@literal @Persistent}
322: * class Pet {
323: * boolean isCatNotDog;
324: * ...
325: * }
326: *
327: * // The new classes. A new version number must be assigned to the Pet class.
328: * //
329: * {@literal @Persistent(version=1)}
330: * class Pet {
331: * ...
332: * }
333: * {@literal @Persistent}
334: * class Cat extends Pet {
335: * ...
336: * }
337: * {@literal @Persistent}
338: * class Dog extends Pet {
339: * ...
340: * }
341: *
342: * class MyConversion5 implements Conversion {
343: * private transient RawType newPetType;
344: * private transient RawType dogType;
345: * private transient RawType catType;
346: *
347: * public void initialize(EntityModel model) {
348: * newPetType = model.getRawType(Pet.class.getName());
349: * dogType = model.getRawType(Dog.class.getName());
350: * catType = model.getRawType(Cat.class.getName());
351: * }
352: *
353: * public Object convert(Object fromValue) {
354: * RawObject pet = (RawObject) fromValue;
355: * {@literal Map<String,Object> petValues = pet.getValues();}
356: * Boolean isCat = (Boolean) petValues.remove("isCatNotDog");
357: * RawObject newPet = new RawObject(newPetType, petValues,
358: * pet.getSuper());
359: * RawType newSubType = isCat ? catType : dogType;
360: * return new RawObject(newSubType, Collections.emptyMap(), newPet);
361: * }
362: *
363: * {@code @Override}
364: * public boolean equals(Object o) {
365: * return o instanceof MyConversion5;
366: * }
367: * }
368: *
369: * // Create a class converter mutation.
370: * //
371: * Converter converter = new Converter(Pet.class.getName(), 0,
372: * new MyConversion5());
373: *
374: * // Configure the converter as described {@link Mutations here}.</pre>
375: *
376: * <p>The primary limitation of a conversion is that it may access at most a
377: * single entity instance at one time. Conversions involving multiple entities
378: * at once may be made by performing a <a
379: * href="package-summary.html#storeConversion">store conversion</a>.</p>
380: *
381: * @see com.sleepycat.persist.evolve Class Evolution
382: * @author Mark Hayes
383: */
384: public interface Conversion extends Serializable {
385:
386: /**
387: * Initializes the conversion, allowing it to obtain raw type information
388: * from the entity model.
389: */
390: void initialize(EntityModel model);
391:
392: /**
393: * Converts an old version of an object value to conform to the current
394: * class or field definition.
395: *
396: * <p>If a {@link RuntimeException} is thrown by this method, it will be
397: * thrown to the original caller. Similarly, a {@link
398: * IllegalArgumentException} will be thrown to the original caller if the
399: * object returned by this method does not conform to current class
400: * definitions.</p>
401: *
402: * <p>The class of the input and output object may be one of the simple
403: * types or {@link RawObject}. For primitive types, the primitive wrapper
404: * class is used.</p>
405: *
406: * @param fromValue the object value being converted. The type of this
407: * value is defined by the old class version that is being converted.
408: *
409: * @return the converted object. The type of this value must conform to
410: * a current class definition. If this is a class conversion, it must
411: * be the current version of the class. If this is a field conversion, it
412: * must be of a type compatible with the current declared type of the
413: * field.
414: */
415: Object convert(Object fromValue);
416:
417: /**
418: * The standard {@code equals} method that must be implemented by
419: * conversion class.
420: *
421: * <p>When mutations are specified when opening a store, the specified and
422: * previously stored mutations are compared for equality. If they are
423: * equal, there is no need to replace the existing mutations in the stored
424: * catalog. To accurately determine equality, the conversion class must
425: * implement the {@code equals} method.</p>
426: *
427: * <p>If the {@code equals} method is not explicitly implemented by the
428: * conversion class or a superclass other than {@code Object}, {@code
429: * IllegalArgumentException} will be thrown when the store is opened.</p>
430: *
431: * <p>Normally whenever {@code equals} is implemented the {@code hashCode}
432: * method should also be implemented to support hash sets and maps.
433: * However, hash sets and maps containing <code>Conversion</code> objects
434: * are not used by the DPL and therefore the DPL does not require
435: * {@code hashCode} to be implemented.</p>
436: */
437: boolean equals(Object other);
438: }
|