1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2002,2008 Oracle. All rights reserved. 5 * 6 * $Id: Evolver.java,v 1.2 2008/02/08 20:12:37 mark Exp $ 7 */ 8 9package com.sleepycat.persist.impl; 10 11import java.io.FileNotFoundException; 12import java.util.ArrayList; 13import java.util.List; 14import java.util.HashMap; 15import java.util.HashSet; 16import java.util.IdentityHashMap; 17import java.util.Map; 18import java.util.Set; 19 20import com.sleepycat.compat.DbCompat; 21import com.sleepycat.db.DatabaseException; 22import com.sleepycat.db.Transaction; 23import com.sleepycat.persist.evolve.Converter; 24import com.sleepycat.persist.evolve.Deleter; 25import com.sleepycat.persist.evolve.Mutation; 26import com.sleepycat.persist.evolve.Mutations; 27import com.sleepycat.persist.evolve.Renamer; 28import com.sleepycat.persist.model.SecondaryKeyMetadata; 29 30/** 31 * Evolves each old format that is still relevant if necessary, using Mutations 32 * to configure deleters, renamers, and converters. 33 * 34 * @author Mark Hayes 35 */ 36class Evolver { 37 38 static final int EVOLVE_NONE = 0; 39 static final int EVOLVE_NEEDED = 1; 40 static final int EVOLVE_FAILURE = 2; 41 42 private PersistCatalog catalog; 43 private String storePrefix; 44 private Mutations mutations; 45 private Map<String,Format> newFormats; 46 private boolean forceEvolution; 47 private boolean disallowClassChanges; 48 private boolean nestedFormatsChanged; 49 private Map<Format,Format> changedFormats; 50 private StringBuilder errors; 51 private Set<String> deleteDbs; 52 private Map<String,String> renameDbs; 53 private Map<Format,Format> renameFormats; 54 private Map<Integer,Boolean> evolvedFormats; 55 private List<Format> unprocessedFormats; 56 private Map<Format,Set<Format>> subclassMap; 57 58 Evolver(PersistCatalog catalog, 59 String storePrefix, 60 Mutations mutations, 61 Map<String,Format> newFormats, 62 boolean forceEvolution, 63 boolean disallowClassChanges) { 64 this.catalog = catalog; 65 this.storePrefix = storePrefix; 66 this.mutations = mutations; 67 this.newFormats = newFormats; 68 this.forceEvolution = forceEvolution; 69 this.disallowClassChanges = disallowClassChanges; 70 changedFormats = new IdentityHashMap<Format,Format>(); 71 errors = new StringBuilder(); 72 deleteDbs = new HashSet<String>(); 73 renameDbs = new HashMap<String,String>(); 74 renameFormats = new IdentityHashMap<Format,Format>(); 75 evolvedFormats = new HashMap<Integer,Boolean>(); 76 unprocessedFormats = new ArrayList<Format>(); 77 subclassMap = catalog.getSubclassMap(); 78 } 79 80 final Mutations getMutations() { 81 return mutations; 82 } 83 84 /** 85 * Returns whether any formats were changed during evolution, and therefore 86 * need to be stored in the catalog. 87 */ 88 boolean areFormatsChanged() { 89 return !changedFormats.isEmpty(); 90 } 91 92 /** 93 * Returns whether the given format was changed during evolution. 94 */ 95 boolean isFormatChanged(Format format) { 96 return changedFormats.containsKey(format); 97 } 98 99 private void setFormatsChanged(Format oldFormat) { 100 checkClassChangesAllowed(oldFormat); 101 changedFormats.put(oldFormat, oldFormat); 102 nestedFormatsChanged = true; 103 /* PersistCatalog.expectNoClassChanges is true in unit tests only. */ 104 if (PersistCatalog.expectNoClassChanges) { 105 throw new IllegalStateException("expectNoClassChanges"); 106 } 107 } 108 109 private void checkClassChangesAllowed(Format oldFormat) { 110 if (disallowClassChanges) { 111 throw new IllegalStateException 112 ("When performing an upgrade changes are not allowed " + 113 "but were made to: " + oldFormat.getClassName()); 114 } 115 } 116 117 /** 118 * Returns the set of formats for a specific superclass format, or null if 119 * the superclass is not a complex type or has not subclasses. 120 */ 121 Set<Format> getSubclassFormats(Format superFormat) { 122 return subclassMap.get(superFormat); 123 } 124 125 /** 126 * Returns an error string if any mutations are invalid or missing, or 127 * returns null otherwise. If non-null is returned, the store may not be 128 * opened. 129 */ 130 String getErrors() { 131 if (errors.length() > 0) { 132 return errors.toString(); 133 } else { 134 return null; 135 } 136 } 137 138 /** 139 * Adds a newline and the given error. 140 */ 141 private void addError(String error) { 142 if (errors.length() > 0) { 143 errors.append("\n---\n"); 144 } 145 errors.append(error); 146 } 147 148 private String getClassVersionLabel(Format format, String prefix) { 149 if (format != null) { 150 return prefix + 151 " class: " + format.getClassName() + 152 " version: " + format.getVersion(); 153 } else { 154 return ""; 155 } 156 } 157 158 /** 159 * Adds a specified error when no specific mutation is involved. 160 */ 161 void addEvolveError(Format oldFormat, 162 Format newFormat, 163 String scenario, 164 String error) { 165 checkClassChangesAllowed(oldFormat); 166 if (scenario == null) { 167 scenario = "Error"; 168 } 169 addError(scenario + " when evolving" + 170 getClassVersionLabel(oldFormat, "") + 171 getClassVersionLabel(newFormat, " to") + 172 " Error: " + error); 173 } 174 175 /** 176 * Adds an error for an invalid mutation. 177 */ 178 void addInvalidMutation(Format oldFormat, 179 Format newFormat, 180 Mutation mutation, 181 String error) { 182 checkClassChangesAllowed(oldFormat); 183 addError("Invalid mutation: " + mutation + 184 getClassVersionLabel(oldFormat, " For") + 185 getClassVersionLabel(newFormat, " New") + 186 " Error: " + error); 187 } 188 189 /** 190 * Adds an error for a missing mutation. 191 */ 192 void addMissingMutation(Format oldFormat, 193 Format newFormat, 194 String error) { 195 checkClassChangesAllowed(oldFormat); 196 addError("Mutation is missing to evolve" + 197 getClassVersionLabel(oldFormat, "") + 198 getClassVersionLabel(newFormat, " to") + 199 " Error: " + error); 200 } 201 202 /** 203 * Called by PersistCatalog for all non-entity formats. 204 */ 205 void addNonEntityFormat(Format oldFormat) { 206 unprocessedFormats.add(oldFormat); 207 } 208 209 /** 210 * Called by PersistCatalog after calling evolveFormat or 211 * addNonEntityFormat for all old formats. 212 * 213 * We do not require deletion of an unreferenced class for three 214 * reasons: 1) built-in proxy classes may not be referenced, 2) the 215 * user may wish to declare persistent classes that are not yet used. 216 */ 217 void finishEvolution() { 218 /* Process unreferenced classes. */ 219 for (Format oldFormat : unprocessedFormats) { 220 oldFormat.setUnused(true); 221 Integer oldFormatId = oldFormat.getId(); 222 if (!evolvedFormats.containsKey(oldFormatId)) { 223 evolvedFormats.put(oldFormatId, true); 224 boolean result = evolveFormatInternal(oldFormat); 225 evolvedFormats.put(oldFormatId, result); 226 } 227 } 228 } 229 230 /** 231 * Called by PersistCatalog for all entity formats, and by Format.evolve 232 * methods for all potentially referenced non-entity formats. 233 */ 234 boolean evolveFormat(Format oldFormat) { 235 boolean result; 236 boolean trackEntityChanges = 237 oldFormat.getLatestVersion().getEntityFormat() != null; 238 boolean saveNestedFormatsChanged = nestedFormatsChanged; 239 if (trackEntityChanges) { 240 nestedFormatsChanged = false; 241 } 242 Integer oldFormatId = oldFormat.getId(); 243 if (evolvedFormats.containsKey(oldFormatId)) { 244 result = evolvedFormats.get(oldFormatId); 245 } else { 246 evolvedFormats.put(oldFormatId, true); 247 result = evolveFormatInternal(oldFormat); 248 evolvedFormats.put(oldFormatId, result); 249 } 250 if (oldFormat.getLatestVersion().isNew()) { 251 nestedFormatsChanged = true; 252 } 253 if (trackEntityChanges) { 254 if (nestedFormatsChanged) { 255 Format latest = oldFormat.getLatestVersion().getEntityFormat(); 256 if (latest != null) { 257 latest.setEvolveNeeded(true); 258 } 259 } 260 nestedFormatsChanged = saveNestedFormatsChanged; 261 } 262 return result; 263 } 264 265 /** 266 * Tries to evolve a given existing format to the current version of the 267 * class and returns false if an invalid mutation is encountered or the 268 * configured mutations are not sufficient. 269 */ 270 private boolean evolveFormatInternal(Format oldFormat) { 271 272 /* Predefined formats and deleted classes never need evolving. */ 273 if (Format.isPredefined(oldFormat) || oldFormat.isDeleted()) { 274 return true; 275 } 276 277 /* Get class mutations. */ 278 String oldName = oldFormat.getClassName(); 279 int oldVersion = oldFormat.getVersion(); 280 Renamer renamer = mutations.getRenamer(oldName, oldVersion, null); 281 Deleter deleter = mutations.getDeleter(oldName, oldVersion, null); 282 Converter converter = 283 mutations.getConverter(oldName, oldVersion, null); 284 if (deleter != null && (converter != null || renamer != null)) { 285 addInvalidMutation 286 (oldFormat, null, deleter, 287 "Class Deleter not allowed along with a Renamer or " + 288 "Converter for the same class"); 289 return false; 290 } 291 292 /* 293 * For determining the new name, arrays get special treatment. The 294 * component format is evolved in the process, and we disallow muations 295 * for arrays. 296 */ 297 String newName; 298 if (oldFormat.isArray()) { 299 if (deleter != null || converter != null || renamer != null) { 300 Mutation mutation = (deleter != null) ? deleter : 301 ((converter != null) ? converter : renamer); 302 addInvalidMutation 303 (oldFormat, null, mutation, 304 "Mutations not allowed for an array"); 305 return false; 306 } 307 Format compFormat = oldFormat.getComponentType(); 308 if (!evolveFormat(compFormat)) { 309 return false; 310 } 311 Format latest = compFormat.getLatestVersion(); 312 if (latest != compFormat) { 313 newName = (latest.isArray() ? "[" : "[L") + 314 latest.getClassName() + ';'; 315 } else { 316 newName = oldName; 317 } 318 } else { 319 newName = (renamer != null) ? renamer.getNewName() : oldName; 320 } 321 322 /* Try to get the new class format. Save exception for later. */ 323 Format newFormat; 324 String newFormatException; 325 try { 326 Class newClass = SimpleCatalog.classForName(newName); 327 try { 328 newFormat = catalog.createFormat(newClass, newFormats); 329 assert newFormat != oldFormat : newFormat.getClassName(); 330 newFormatException = null; 331 } catch (Exception e) { 332 newFormat = null; 333 newFormatException = e.toString(); 334 } 335 } catch (ClassNotFoundException e) { 336 newFormat = null; 337 newFormatException = e.toString(); 338 } 339 340 if (newFormat != null) { 341 342 /* 343 * If the old format is not the existing latest version and the new 344 * format is not an existing format, then we must evolve the latest 345 * old version to the new format first. We cannot evolve old 346 * format to a new format that may be discarded because it is equal 347 * to the latest existing format (which will remain the current 348 * version). 349 */ 350 if (oldFormat != oldFormat.getLatestVersion() && 351 newFormat.getPreviousVersion() == null) { 352 assert newFormats.containsValue(newFormat); 353 Format oldLatestFormat = oldFormat.getLatestVersion(); 354 evolveFormat(oldLatestFormat); 355 if (oldLatestFormat == oldLatestFormat.getLatestVersion()) { 356 assert !newFormats.containsValue(newFormat); 357 /* newFormat equals oldLatestFormat and was discarded. */ 358 newFormat = oldLatestFormat; 359 } 360 } 361 362 /* 363 * If the old format was previously evolved to the new format 364 * (which means the new format is actually an existing format), 365 * then there is nothing to do. This is the case where no class 366 * changes were made. 367 * 368 * However, if mutations were specified when opening the catalog 369 * that are different than the mutations last used, then we must 370 * force the re-evolution of all old formats. 371 */ 372 if (!forceEvolution && 373 newFormat == oldFormat.getLatestVersion()) { 374 return true; 375 } 376 } 377 378 /* Apply class Renamer and continue if successful. */ 379 if (renamer != null) { 380 if (!applyRenamer(renamer, oldFormat, newFormat)) { 381 return false; 382 } 383 } 384 385 /* Apply class Converter and return. */ 386 if (converter != null) { 387 if (oldFormat.isEntity()) { 388 if (newFormat == null || !newFormat.isEntity()) { 389 addInvalidMutation 390 (oldFormat, newFormat, deleter, 391 "Class converter not allowed for an entity class " + 392 "that is no longer present or not having an " + 393 "@Entity annotation"); 394 return false; 395 } 396 if (!oldFormat.evolveMetadata(newFormat, converter, this)) { 397 return false; 398 } 399 } 400 return applyConverter(converter, oldFormat, newFormat); 401 } 402 403 /* Apply class Deleter and return. */ 404 boolean needDeleter = 405 (newFormat == null) || 406 (newFormat.isEntity() != oldFormat.isEntity()); 407 if (deleter != null) { 408 if (!needDeleter) { 409 addInvalidMutation 410 (oldFormat, newFormat, deleter, 411 "Class deleter not allowed when the class and its " + 412 "@Entity or @Persistent annotation is still present"); 413 return false; 414 } 415 return applyDeleter(deleter, oldFormat, newFormat); 416 } else { 417 if (needDeleter) { 418 if (newFormat == null) { 419 assert newFormatException != null; 420 /* FindBugs newFormat known to be null excluded. */ 421 addMissingMutation 422 (oldFormat, newFormat, newFormatException); 423 } else { 424 addMissingMutation 425 (oldFormat, newFormat, 426 "@Entity switched to/from @Persistent"); 427 } 428 return false; 429 } 430 } 431 432 /* 433 * Class-level mutations have been applied. Now apply field mutations 434 * (for complex classes) or special conversions (enum conversions, for 435 * example) by calling the old format's evolve method. 436 */ 437 return oldFormat.evolve(newFormat, this); 438 } 439 440 /** 441 * Use the old format and discard the new format. Called by 442 * Format.evolve when the old and new formats are identical. 443 */ 444 void useOldFormat(Format oldFormat, Format newFormat) { 445 Format renamedFormat = renameFormats.get(oldFormat); 446 if (renamedFormat != null) { 447 448 /* 449 * The format was renamed but, because this method is called, we 450 * know that no other class changes were made. Use the new/renamed 451 * format as the reader. 452 */ 453 assert renamedFormat == newFormat; 454 useEvolvedFormat(oldFormat, renamedFormat, renamedFormat); 455 } else if (newFormat != null && 456 oldFormat.getVersion() != newFormat.getVersion()) { 457 /* The user wants a new version number, but no other changes. */ 458 useEvolvedFormat(oldFormat, newFormat, newFormat); 459 } else { 460 /* The new format is discarded. */ 461 catalog.useExistingFormat(oldFormat); 462 if (newFormat != null) { 463 newFormats.remove(newFormat.getClassName()); 464 } 465 } 466 } 467 468 /** 469 * Install an evolver Reader in the old format. Called by Format.evolve 470 * when the old and new formats are not identical. 471 */ 472 void useEvolvedFormat(Format oldFormat, 473 Reader evolveReader, 474 Format newFormat) { 475 oldFormat.setReader(evolveReader); 476 if (newFormat != null) { 477 oldFormat.setLatestVersion(newFormat); 478 } 479 setFormatsChanged(oldFormat); 480 } 481 482 private boolean applyRenamer(Renamer renamer, 483 Format oldFormat, 484 Format newFormat) { 485 if (!checkUpdatedVersion(renamer, oldFormat, newFormat)) { 486 return false; 487 } 488 if (oldFormat.isEntity() && oldFormat.isCurrentVersion()) { 489 String newClassName = newFormat.getClassName(); 490 String oldClassName = oldFormat.getClassName(); 491 /* Queue the renaming of the primary and secondary databases. */ 492 renameDbs.put 493 (Store.makePriDbName(storePrefix, oldClassName), 494 Store.makePriDbName(storePrefix, newClassName)); 495 for (SecondaryKeyMetadata keyMeta : 496 oldFormat.getEntityMetadata().getSecondaryKeys().values()) { 497 String keyName = keyMeta.getKeyName(); 498 renameDbs.put 499 (Store.makeSecDbName(storePrefix, oldClassName, keyName), 500 Store.makeSecDbName(storePrefix, newClassName, keyName)); 501 } 502 } 503 504 /* 505 * Link the old format to the renamed format so that we can detect the 506 * rename in useOldFormat. 507 */ 508 renameFormats.put(oldFormat, newFormat); 509 510 setFormatsChanged(oldFormat); 511 return true; 512 } 513 514 /** 515 * Called by ComplexFormat when a secondary key name is changed. 516 */ 517 void renameSecondaryDatabase(Format oldFormat, 518 Format newFormat, 519 String oldKeyName, 520 String newKeyName) { 521 renameDbs.put 522 (Store.makeSecDbName 523 (storePrefix, oldFormat.getClassName(), oldKeyName), 524 Store.makeSecDbName 525 (storePrefix, newFormat.getClassName(), newKeyName)); 526 } 527 528 private boolean applyDeleter(Deleter deleter, 529 Format oldFormat, 530 Format newFormat) { 531 if (!checkUpdatedVersion(deleter, oldFormat, newFormat)) { 532 return false; 533 } 534 if (oldFormat.isEntity() && oldFormat.isCurrentVersion()) { 535 /* Queue the deletion of the primary and secondary databases. */ 536 String className = oldFormat.getClassName(); 537 deleteDbs.add(Store.makePriDbName(storePrefix, className)); 538 for (SecondaryKeyMetadata keyMeta : 539 oldFormat.getEntityMetadata().getSecondaryKeys().values()) { 540 deleteDbs.add(Store.makeSecDbName 541 (storePrefix, className, keyMeta.getKeyName())); 542 } 543 } 544 545 /* 546 * Set the format to deleted last, so that the above test using 547 * isCurrentVersion works properly. 548 */ 549 oldFormat.setDeleted(true); 550 if (newFormat != null) { 551 oldFormat.setLatestVersion(newFormat); 552 } 553 554 setFormatsChanged(oldFormat); 555 return true; 556 } 557 558 /** 559 * Called by ComplexFormat when a secondary key is dropped. 560 */ 561 void deleteSecondaryDatabase(Format oldFormat, String keyName) { 562 deleteDbs.add(Store.makeSecDbName 563 (storePrefix, oldFormat.getClassName(), keyName)); 564 } 565 566 private boolean applyConverter(Converter converter, 567 Format oldFormat, 568 Format newFormat) { 569 if (!checkUpdatedVersion(converter, oldFormat, newFormat)) { 570 return false; 571 } 572 Reader reader = new ConverterReader(converter); 573 useEvolvedFormat(oldFormat, reader, newFormat); 574 return true; 575 } 576 577 boolean isClassConverted(Format format) { 578 return format.getReader() instanceof ConverterReader; 579 } 580 581 private boolean checkUpdatedVersion(Mutation mutation, 582 Format oldFormat, 583 Format newFormat) { 584 if (newFormat != null && 585 !oldFormat.isEnum() && 586 newFormat.getVersion() <= oldFormat.getVersion()) { 587 addInvalidMutation 588 (oldFormat, newFormat, mutation, 589 "A new higher version number must be assigned"); 590 return false; 591 } else { 592 return true; 593 } 594 } 595 596 boolean checkUpdatedVersion(String scenario, 597 Format oldFormat, 598 Format newFormat) { 599 if (newFormat != null && 600 !oldFormat.isEnum() && 601 newFormat.getVersion() <= oldFormat.getVersion()) { 602 addEvolveError 603 (oldFormat, newFormat, scenario, 604 "A new higher version number must be assigned"); 605 return false; 606 } else { 607 return true; 608 } 609 } 610 611 void renameAndRemoveDatabases(Store store, Transaction txn) 612 throws DatabaseException { 613 614 for (String dbName : deleteDbs) { 615 try { 616 String[] fileAndDbNames = store.parseDbName(dbName); 617 DbCompat.removeDatabase 618 (store.getEnvironment(), txn, 619 fileAndDbNames[0], fileAndDbNames[1]); 620 } catch (FileNotFoundException ignored) { 621 } 622 } 623 for (Map.Entry<String,String> entry : renameDbs.entrySet()) { 624 String oldName = entry.getKey(); 625 String newName = entry.getValue(); 626 try { 627 String[] oldFileAndDbNames = store.parseDbName(oldName); 628 String[] newFileAndDbNames = store.parseDbName(newName); 629 DbCompat.renameDatabase 630 (store.getEnvironment(), txn, 631 oldFileAndDbNames[0], oldFileAndDbNames[1], 632 newFileAndDbNames[0], newFileAndDbNames[1]); 633 } catch (FileNotFoundException ignored) { 634 } 635 } 636 } 637 638 /** 639 * Evolves a primary key field or composite key field. 640 */ 641 int evolveRequiredKeyField(Format oldParent, 642 Format newParent, 643 FieldInfo oldField, 644 FieldInfo newField) { 645 int result = EVOLVE_NONE; 646 String oldName = oldField.getName(); 647 final String FIELD_KIND = 648 "primary key field or composite key class field"; 649 final String FIELD_LABEL = 650 FIELD_KIND + ": " + oldName; 651 652 if (newField == null) { 653 addMissingMutation 654 (oldParent, newParent, 655 "Field is missing and deletion is not allowed for a " + 656 FIELD_LABEL); 657 return EVOLVE_FAILURE; 658 } 659 660 /* Check field mutations. Only a Renamer is allowed. */ 661 Deleter deleter = mutations.getDeleter 662 (oldParent.getClassName(), oldParent.getVersion(), oldName); 663 if (deleter != null) { 664 addInvalidMutation 665 (oldParent, newParent, deleter, 666 "Deleter is not allowed for a " + FIELD_LABEL); 667 return EVOLVE_FAILURE; 668 } 669 Converter converter = mutations.getConverter 670 (oldParent.getClassName(), oldParent.getVersion(), oldName); 671 if (converter != null) { 672 addInvalidMutation 673 (oldParent, newParent, converter, 674 "Converter is not allowed for a " + FIELD_LABEL); 675 return EVOLVE_FAILURE; 676 } 677 Renamer renamer = mutations.getRenamer 678 (oldParent.getClassName(), oldParent.getVersion(), oldName); 679 String newName = newField.getName(); 680 if (renamer != null) { 681 if (!renamer.getNewName().equals(newName)) { 682 addInvalidMutation 683 (oldParent, newParent, converter, 684 "Converter is not allowed for a " + FIELD_LABEL); 685 return EVOLVE_FAILURE; 686 } 687 result = EVOLVE_NEEDED; 688 } else { 689 if (!oldName.equals(newName)) { 690 addMissingMutation 691 (oldParent, newParent, 692 "Renamer is required when field name is changed from: " + 693 oldName + " to: " + newName); 694 return EVOLVE_FAILURE; 695 } 696 } 697 698 /* 699 * Evolve the declared version of the field format. 700 */ 701 Format oldFieldFormat = oldField.getType(); 702 if (!evolveFormat(oldFieldFormat)) { 703 return EVOLVE_FAILURE; 704 } 705 Format oldLatestFormat = oldFieldFormat.getLatestVersion(); 706 Format newFieldFormat = newField.getType(); 707 708 if (oldLatestFormat.getClassName().equals 709 (newFieldFormat.getClassName())) { 710 /* Formats are identical. */ 711 return result; 712 } else if ((oldLatestFormat.getWrapperFormat() != null && 713 oldLatestFormat.getWrapperFormat().getId() == 714 newFieldFormat.getId()) || 715 (newFieldFormat.getWrapperFormat() != null && 716 newFieldFormat.getWrapperFormat().getId() == 717 oldLatestFormat.getId())) { 718 /* Primitive <-> primitive wrapper type change. */ 719 return EVOLVE_NEEDED; 720 } else { 721 /* Type was changed incompatibly. */ 722 addEvolveError 723 (oldParent, newParent, 724 "Type may not be changed for a " + FIELD_KIND, 725 "Old field type: " + oldLatestFormat.getClassName() + 726 " is not compatible with the new type: " + 727 newFieldFormat.getClassName() + 728 " for field: " + oldName); 729 return EVOLVE_FAILURE; 730 } 731 } 732} 733