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