1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2002-2009 Oracle. All rights reserved. 5 * 6 * $Id$ 7 */ 8 9package com.sleepycat.persist.evolve; 10 11import java.io.Serializable; 12 13import com.sleepycat.persist.model.EntityModel; 14import com.sleepycat.persist.raw.RawObject; 15import com.sleepycat.persist.raw.RawType; 16 17/** 18 * Converts an old version of an object value to conform to the current class 19 * or field definition. 20 * 21 * <p>The {@code Conversion} interface is implemented by the user. A 22 * {@code Conversion} instance is passed to the {@link Converter#Converter} 23 * constructor.</p> 24 * 25 * <p>The {@code Conversion} interface extends {@link Serializable} and the 26 * {@code Conversion} instance is serialized for storage using standard Java 27 * serialization. Normally, the {@code Conversion} class should only have 28 * transient fields that are initialized in the {@link #initialize} method. 29 * While non-transient fields are allowed, care must be taken to only include 30 * fields that are serializable and will not pull in large amounts of data.</p> 31 * 32 * <p>When a class conversion is specified, two special considerations 33 * apply:</p> 34 * <ol> 35 * <li>A class conversion is only applied when to instances of that class. The 36 * conversion will not be applied when the class when it appears as a 37 * superclass of the instance's class. In this case, a conversion for the 38 * instance's class must also be specified.</li> 39 * <li>Although field renaming (as well as all other changes) is handled by the 40 * conversion method, a field Renamer is still needed when a secondary key 41 * field is renamed and field Deleter is still needed when a secondary key 42 * field is deleted. This is necessary for evolution of the metadata; 43 * specifically, if the key name changes the database must be renamed and if 44 * the key field is deleted the secondary database must be deleted.</li> 45 * </ol> 46 * 47 * <p>The {@code Conversion} class must implement the standard equals method. 48 * See {@link #equals} for more information.</p> 49 * 50 * <p>Conversions of simple types are generally simple. For example, a {@code 51 * String} field that contains only integer values can be easily converted to 52 * an {@code int} field:</p> 53 * <pre class="code"> 54 * // The old class. Version 0 is implied. 55 * // 56 * {@literal @Persistent} 57 * class Address { 58 * String zipCode; 59 * ... 60 * } 61 * 62 * // The new class. A new version number must be assigned. 63 * // 64 * {@literal @Persistent(version=1)} 65 * class Address { 66 * int zipCode; 67 * ... 68 * } 69 * 70 * // The conversion class. 71 * // 72 * class MyConversion1 implements Conversion { 73 * 74 * public void initialize(EntityModel model) { 75 * // No initialization needed. 76 * } 77 * 78 * public Object convert(Object fromValue) { 79 * return Integer.valueOf((String) fromValue); 80 * } 81 * 82 * {@code @Override} 83 * public boolean equals(Object o) { 84 * return o instanceof MyConversion1; 85 * } 86 * } 87 * 88 * // Create a field converter mutation. 89 * // 90 * Converter converter = new Converter(Address.class.getName(), 0, 91 * "zipCode", new MyConversion1()); 92 * 93 * // Configure the converter as described {@link Mutations here}.</pre> 94 * 95 * <p>A conversion may perform arbitrary transformations on an object. For 96 * example, a conversion may transform a single String address field into an 97 * Address object containing four fields for street, city, state and zip 98 * code.</p> 99 * <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 */ 384public 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} 439