mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

kenneth_suter
19.28.2007 efde227b9d0180122362133a750c6b322601c883
This initial implementation lays most of the groundwork necessary for signaling information and actions to the user during upgrade and reversion.  However the only public effect this code has on the current upgrade tool is an informational dialog dialog if an upgrade is attempted from a build prior to one of the issues (2049, 1582, 890).

With this commit:

- start-ds -F now advertises 'Upgrade Event IDs' which are the IDs of issues defined in VersionCompatibilityIssues that theoretically would have been encountered up to the current build.

- As mentioned above VersionCompatibilityIssue defines an issue and serves as the repository for issues. I tried to provide as many comments as possible to make it apparent how to define issues in the future.

- The upgrader's VersionOracle is responsible for correlating the version issues with actions to be taken by the upgrader or yet-to-be implemented reversion tools and presenting them to the user during the operation.

4 files added
12 files modified
1676 ■■■■■ changed files
opends/src/quicksetup/org/opends/quicksetup/BuildInformation.java 37 ●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/CliUserInteraction.java 29 ●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/Constants.java 24 ●●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/UserInteraction.java 10 ●●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/resources/Resources.properties 43 ●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/ui/GuiUserInteraction.java 19 ●●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/upgrader/UpgradeOracle.java 229 ●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/upgrader/Upgrader.java 20 ●●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/upgrader/VersionOracle.java 376 ●●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/util/Utils.java 43 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DirectoryServer.java 6 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/MessageHandler.java 7 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/VersionMessages.java 151 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/BuildVersion.java 131 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/StaticUtils.java 21 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/VersionCompatibilityIssue.java 530 ●●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/BuildInformation.java
@@ -35,6 +35,10 @@
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.io.InputStream;
@@ -48,6 +52,9 @@
 */
public class BuildInformation implements Comparable {
  static private final Logger LOG =
          Logger.getLogger(BuildInformation.class.getName());
  // These string values must be synchronized with Directory
  // Server's main method.  These string values are considered
  // stable by the server team and not candidates for
@@ -59,6 +66,7 @@
  static private final String POINT_VERSION = "Point Version";
  static private final String REVISION_NUMBER = "Revision Number";
  static private final String VERSION_QUALIFIER = "Version Qualifier";
  static private final String INCOMPATIBILITY_EVENTS = "Upgrade Event IDs";
  static private final String FIX_IDS = "Fix IDs";
  static private final String DEBUG_BUILD = "Debug Build";
  static private final String BUILD_OS = "Build OS";
@@ -68,7 +76,6 @@
  static private final String BUILD_JVM_VERSION = "Build JVM Version";
  static private final String BUILD_JVM_VENDOR = "Build JVM Vendor";
  /**
   * Reads build information for a particular installation by reading the
   * output from invoking the start-ds tool with the full information option.
@@ -264,6 +271,28 @@
  }
  /**
   * Gets the set of IDs representing <code>IncompatibleVersionEvents</code>.
   * @return set of integers representing events
   * @see org.opends.server.util.VersionCompatibilityIssue
   */
  public Set<Integer> getIncompatibilityEventIds() {
    HashSet<Integer> ids = null;
    String idString = values.get(INCOMPATIBILITY_EVENTS);
    if (idString != null) {
      ids = new HashSet<Integer>();
      String[] sa = idString.split(",");
      for (String s : sa) {
        try {
          ids.add(Integer.parseInt(s));
        } catch (NumberFormatException nfe) {
          LOG.log(Level.INFO, "invalid upgrade incompatability ID " + s);
        }
      }
    }
    return ids;
  }
  /**
   * Returns a build string representation of this object.  A build
   * number is a string formatted MAJOR.MINOR.POINT.REVISION where
   * MAJOR, MINOR, POINT and REVISION are integers.
@@ -331,9 +360,9 @@
   * {@inheritDoc}
   */
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    return compareTo(o) == 0;
    return this == o ||
            !(o == null || getClass() != o.getClass()) &&
                    compareTo(o) == 0;
  }
  /**
opends/src/quicksetup/org/opends/quicksetup/CliUserInteraction.java
@@ -94,10 +94,9 @@
              viewDetailsOption != null ? viewDetailsOption : "View Details"));
    }
    println(summary);
    println();
    println(Utils.stripHtml(summary));
    println();
    println(Utils.stripHtml(details));
    println(details);
    String returnValue = null;
    while (returnValue == null) {
@@ -106,8 +105,8 @@
        println(o);
      }
      System.out.print(getMsg("cli-uninstall-confirm-prompt",
          new String[] {"Enter a number or press Enter to accept the default",
                  Integer.toString(defInt)}));
              "Enter a number or press Enter to accept the default",
              Integer.toString(defInt)));
      System.out.flush();
@@ -133,7 +132,20 @@
    return returnValue;
  }
  /**
   * {@inheritDoc}
   */
  public String createUnorderedList(List list) {
    StringBuilder sb = new StringBuilder();
    if (list != null) {
      for (Object o : list) {
        sb.append(/*bullet=*/"\u2022 ");
        sb.append(o.toString());
        sb.append(Constants.LINE_SEPARATOR);
      }
    }
    return sb.toString();
  }
  private String createOption(int index, String option) {
    return new StringBuilder().
@@ -147,7 +159,10 @@
  }
  private void println(String text) {
    out.println(StaticUtils.wrapText(text, Utils.getCommandLineMaxLineWidth()));
    text = Utils.convertHtmlBreakToLineSeparator(text);
    text = Utils.stripHtml(text);
    text = StaticUtils.wrapText(text, Utils.getCommandLineMaxLineWidth());
    out.println(text);
  }
}
opends/src/quicksetup/org/opends/quicksetup/Constants.java
@@ -51,7 +51,31 @@
  /** HTML italics close tag. */
  public static final String HTML_ITALICS_CLOSE = "</i>";
  /** HTML unordered list open tag. */
  public static final Object HTML_UNORDERED_LIST_OPEN = "<ul>";
  /** HTML unordered list close tag. */
  public static final Object HTML_UNORDERED_LIST_CLOSE = "</ul>";
  /** HTML unordered list open tag. */
  public static final Object HTML_ORDERED_LIST_OPEN = "<ol>";
  /** HTML unordered list close tag. */
  public static final Object HTML_ORDERED_LIST_CLOSE = "</ol>";
  /** HTML list item open tag. */
  public static final String HTML_LIST_ITEM_OPEN = "<li>";
  /** HTML list item close tag. */
  public static final String HTML_LIST_ITEM_CLOSE = "</li>";
  /** Default dynamic name of directory manager. */
  public static final String DIRECTORY_MANAGER_DN = "cn=Directory Manager";
  /** These HTML tags cause a line break in formatted text. */
  public static final String[] BREAKING_TAGS = {
          HTML_LINE_BREAK,
          HTML_LIST_ITEM_CLOSE
  };
}
opends/src/quicksetup/org/opends/quicksetup/UserInteraction.java
@@ -27,6 +27,8 @@
package org.opends.quicksetup;
import java.util.List;
/**
 * This class describes methods for supporting interaction with the user.
 */
@@ -94,4 +96,12 @@
                 String title, MessageType type, String[] options, String def,
                 String viewDetailsOption);
  /**
   * Creates a list appropriate for the presentation implementation.
   *
   * @param list to format
   * @return String representing the list
   */
  String createUnorderedList(List list);
}
opends/src/quicksetup/org/opends/quicksetup/resources/Resources.properties
@@ -1142,18 +1142,39 @@
some database files.<br>If you continue with the setup the contents of these \
database files will be deleted.
upgrade-hypothetical-upgrade-success=Upgrade from version {0} to version {1} \
upgrade-oracle-success=Upgrade from version {0} to version {1} \
  is supported.
upgrade-hypothetical-reversion-success=Reversion from version {0} to version \
  {1} is supported.
upgrade-hypothetical-upgrade-failure=Upgrade from version {0} to version {1} \
upgrade-oracle-action=Upgrade requires manual action
upgrade-oracle-warning=Upgrade warning
upgrade-oracle-info=Upgrade information
upgrade-oracle-unsupported=Upgrade not supported\
  from version {0} to version {1} \
  is not supported.  To upgrade You must uninstall the current server, install \
  the new server, and manually migrate your data.
upgrade-hypothetical-reversion-failure=Reversion from version {0} to version \
  {1} is not supported.  To revert versions you must uninstall the current \
  server, install the new server, and manually migrate your data.
upgrade-hypothetical-versions-the-same=Both the 'upgrade to' and 'upgrade from' \
  numbers are the same: {0}
reversion-oracle-success=Reversion from version {0} to version \
  {1} is supported.
reversion-oracle-ei-action=Reversion from version {0} to version {1} \
  requires further action.
reversion-oracle-failure=reversion from version {0} to version {1} \
  is not supported.  To reversion You must uninstall the current server, install \
  the new server, and manually migrate your data.
oracle-desc-action=This operation requires that you perform specific tasks described \
  in the details section before continuing.
oracle-ei-action-step1=Before starting the operation you should export the entire \
  data set for this server to LDIF format.  <b>If you have not completed this \
  step you should cancel this operation now</b>.
oracle-ei-action-step2=Continue with this operation until this tool has finished.
oracle-ei-action-step3=When this operation is complete, manually delete the files in the 'db' \
  directory.
oracle-ei-action-step4=Reimport that data from the LDIF file that you had created in the first step.
oracle-action-prompt=Have you performed the tasks described?
oracle-info-prompt=Would you like to continue with this operation?
oracle-action-prompt-continue=Yes, Continue
oracle-action-prompt-cancel=No, Cancel
oracle-no-silent=This operation includes specific instructions and/or questions \
  that you must follow.  Silent mode is not supported for this version.
upgrade-mod-no-schema=Processed server modifications \
  (schema checking disabled): {0}
upgrade-mod=Processed server modifications: {0}
@@ -1285,6 +1306,10 @@
installandupgrader-rbupgrade-tooltip=Select to upgrade an existing server \
instance.
general-action-required=Action Required
general-warning=Warning
general-info=Information
general-unsupported=Unsupported
general-checking-data=Checking Data...
general-loading=Loading...
general-see-for-details=See {0} for a detailed log of this operation.
opends/src/quicksetup/org/opends/quicksetup/ui/GuiUserInteraction.java
@@ -38,6 +38,7 @@
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.util.List;
/**
 * This class supports user interactions for a graphical application.
@@ -108,6 +109,7 @@
    sb.append(Utils.breakHtmlString(summary, MAX_CHARS_PER_LINE));
    sb.append(Constants.HTML_BOLD_CLOSE);
    sb.append(Constants.HTML_LINE_BREAK);
    sb.append(Constants.HTML_LINE_BREAK);
    sb.append(Utils.breakHtmlString(details, MAX_CHARS_PER_LINE));
    JEditorPane ep = UIFactory.makeHtmlPane(
            sb.toString(),
@@ -124,6 +126,23 @@
  }
  /**
   * {@inheritDoc}
   */
  public String createUnorderedList(List list) {
    StringBuilder sb = new StringBuilder();
    if (list != null) {
      sb.append(Constants.HTML_UNORDERED_LIST_OPEN);
      for (Object o : list) {
        sb.append(Constants.HTML_LIST_ITEM_OPEN);
        sb.append(o.toString());
        sb.append(Constants.HTML_LIST_ITEM_CLOSE);
      }
      sb.append(Constants.HTML_UNORDERED_LIST_CLOSE);
    }
    return sb.toString();
  }
  /**
   * JOptionPane that controls the number of characters that are allowed
   * to appear on a single line in the input area of the dialog.
   */
opends/src/quicksetup/org/opends/quicksetup/upgrader/UpgradeOracle.java
@@ -27,91 +27,194 @@
package org.opends.quicksetup.upgrader;
import org.opends.quicksetup.i18n.ResourceProvider;
import org.opends.quicksetup.BuildInformation;
import org.opends.quicksetup.UserInteraction;
import org.opends.quicksetup.ApplicationException;
import org.opends.quicksetup.Constants;
import org.opends.server.util.VersionCompatibilityIssue;
import static org.opends.server.util.VersionCompatibilityIssue.*;
import java.util.Set;
import java.util.List;
import java.util.logging.Logger;
import java.util.logging.Level;
/**
 * This class can answer questions important upgrade/reversion questions
 * like 'can I upgrade from verion X to version Y?' and 'if not then why?'.
 * {@link org.opends.quicksetup.upgrader.VersionOracle} specific
 * to upgrade tools.
 */
public class UpgradeOracle {
public class UpgradeOracle extends VersionOracle {
  private BuildInformation currentBuildInfo;
  private BuildInformation newBuildInfo;
  static private final Logger LOG =
          Logger.getLogger(UpgradeOracle.class.getName());
  /**
   * Creates a new instance that can analyze a hypothetical upgrade/reversion
   * operation from one version to another.
   * @param ui UserInteraction for relaying information to the user
   * @param current BuildInformation representing the current version
   * @param neu BuildInformation representing the proposed next version
   */
  public UpgradeOracle(BuildInformation current, BuildInformation neu) {
    this.currentBuildInfo = current;
    this.newBuildInfo = neu;
  public UpgradeOracle(UserInteraction ui,
                       BuildInformation current,
                       BuildInformation neu) {
    super(ui, current, neu);
  }
  /**
   * Indicates whether or not this operation would be considered an
   * upgrade (as opposed to a reversion).
   * @return boolean where true indicates that this would be an upgrade;
   *         false indicates that this would be a reversion.
   * {@inheritDoc}
   */
  public boolean isUpgrade() {
    return currentBuildInfo.compareTo(newBuildInfo) < 0;
  }
  /**
   * Indicates whether or not this operation would be considered an
   * reversion (as opposed to an upgrade).
   * @return boolean where true indicates that this would be a reversion;
   *         false indicates that this would be an upgrade.
   */
  public boolean isReversion() {
    return currentBuildInfo.compareTo(newBuildInfo) > 0;
  }
  /**
   * Indicates whether or not this hypothetical operation should be allowed
   * to happen.
   * @return boolean where true indicates that we are confident that such
   * an operation will succeed
   */
  public boolean isSupported() {
    return !isReversion();
  }
  /**
   * Creates a string summarizing a hypothetical upgrade/reversion
   * from <code>currentVersion</code> to <code>newVersion</code> giving
   * reasons why such an attempt would not be successful.
   * @return String representing a localized message giving a summary of
   * this hypothetical operation.
   */
  public String getLocalizedSummaryMessage() {
    String msg;
    String[] args = { currentBuildInfo.toString(),
            currentBuildInfo.toString() };
    ResourceProvider rp = ResourceProvider.getInstance();
    if (isSupported()) {
      if (isUpgrade()) {
        msg = rp.getMsg("upgrade-hypothetical-upgrade-success", args);
      } else if (isReversion()) {
        msg = rp.getMsg("upgrade-hypothetical-reversion-success", args);
  public void notifyUser() throws ApplicationException {
    String[] args = { currentBuildInfo.toString(), newBuildInfo.toString() };
    String cont = getMsg("oracle-action-prompt-continue");
    String cancel = getMsg("oracle-action-prompt-cancel");
    if (hasIssues()) {
      List<Directive> issues = getIssues();
      if (!isSupported()) {
        if (issues != null) {
          for (VersionOracle.Directive directive : issues) {
            LOG.log(Level.INFO, "Unsupported upgrade details: " +
                    directive.getMessage());
          }
        }
        throw new ApplicationException(ApplicationException.Type.APPLICATION,
                getMsg("upgrade-oracle-unsupported", args), null);
      } else {
        msg = rp.getMsg("upgrade-hypothetical-versions-the-same", args);
        if (ui != null) {
          for (VersionOracle.Directive directive : issues) {
            String title;
            String summary;
            String details;
            String defaultAction;
            UserInteraction.MessageType msgType;
            switch (directive.getType()) {
              case ACTION:
                title = getMsg("general-action-required");
                summary = getMsg("upgrade-oracle-action", args);
                details = directive.getMessage() +
                        Constants.HTML_LINE_BREAK +
                        Constants.HTML_LINE_BREAK +
                        getMsg("oracle-action-prompt");
                msgType = UserInteraction.MessageType.WARNING;
                defaultAction = cancel;
                break;
              case INFO:
                title = getMsg("general-info");
                summary = getMsg("upgrade-oracle-info");
                details = directive.getMessage() +
                        Constants.HTML_LINE_BREAK +
                        Constants.HTML_LINE_BREAK +
                        getMsg("oracle-info-prompt");
                msgType = UserInteraction.MessageType.INFORMATION;
                defaultAction = cont;
                break;
              case WARNING:
                title = getMsg("general-warning");
                summary = getMsg("upgrade-oracle-warning");
                details = directive.getMessage() +
                        Constants.HTML_LINE_BREAK +
                        Constants.HTML_LINE_BREAK +
                        getMsg("oracle-info-prompt");
                msgType = UserInteraction.MessageType.WARNING;
                defaultAction = cont;
                break;
              default:
                LOG.log(Level.INFO, "Unexpected issue type " +
                        directive.getType());
                title = "";
                summary = "";
                details = directive.getMessage();
                msgType = UserInteraction.MessageType.WARNING;
                defaultAction = cont;
            }
            if (cancel.equals(ui.confirm(
                    summary,
                    details,
                    title,
                    msgType,
                    new String[]{cont, cancel},
                    defaultAction))) {
              throw new ApplicationException(
                      ApplicationException.Type.CANCEL,
                      getMsg("upgrade-canceled"), null);
            }
          }
        } else {
          throw new ApplicationException(ApplicationException.Type.APPLICATION,
                  getMsg("oracle-no-silent"), null);
        }
      }
    } else {
      if (isUpgrade()) {
        msg = rp.getMsg("upgrade-hypothetical-upgrade-failure", args);
      } else if (isReversion()) {
        msg = rp.getMsg("upgrade-hypothetical-reversion-failure", args);
      } else {
        msg = rp.getMsg("upgrade-hypothetical-versions-the-same", args);
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  protected String getLocalizedDetailMessage(
          VersionCompatibilityIssue.Cause cause)
  {
    String msg = cause.getLocalizedUpgradeMessage();
    // See if we need to supply a generic message
    Set<VersionCompatibilityIssue.Effect> effects = cause.getEffects();
    // If the import/export effect is present, append the detailed
    // instructions.
    if (effects.contains(Effect.UPGRADE_DATA_EXPORT_AND_REIMPORT_REQUIRED)) {
      if (msg == null) msg = "";
      msg = msg + Constants.HTML_LINE_BREAK +
              ui.createUnorderedList(getExportImportInstructions());
    }
    return msg;
  }
  /**
   * {@inheritDoc}
   */
  protected boolean isActionRequired(VersionCompatibilityIssue.Cause cause) {
    boolean isAction = false;
    if (cause != null) {
      Set<VersionCompatibilityIssue.Effect> effects = cause.getEffects();
      isAction =
              effects.contains(
                      Effect.UPGRADE_DATA_EXPORT_AND_REIMPORT_REQUIRED) ||
                      (effects.contains(
                              Effect.UPGRADE_MANUAL_ACTION_REQUIRED) &&
                              cause.getLocalizedUpgradeMessage() != null);
    }
    return isAction;
  }
  /**
   * {@inheritDoc}
   */
  protected boolean isWarning(VersionCompatibilityIssue.Cause cause) {
    boolean isWarning = false;
    if (cause != null && !isActionRequired(cause)) {
      Set<VersionCompatibilityIssue.Effect> effects = cause.getEffects();
      isWarning = effects.contains(Effect.UPGRADE_SHOW_WARNING_MESSAGE) &&
              cause.getLocalizedUpgradeMessage() != null;
    }
    return isWarning;
  }
  /**
   * {@inheritDoc}
   */
  protected boolean isUnsupported(VersionCompatibilityIssue.Cause cause) {
    boolean isUnsupported = false;
    if (cause != null) {
      Set<VersionCompatibilityIssue.Effect> effects = cause.getEffects();
      for (VersionCompatibilityIssue.Effect effect : effects) {
        switch (effect) {
          case UPGRADE_NOT_POSSIBLE:
            isUnsupported = true; break;
          default:
            // assume not an tion;
        }
      }
    }
    return isUnsupported;
  }
}
opends/src/quicksetup/org/opends/quicksetup/upgrader/Upgrader.java
@@ -725,7 +725,7 @@
        ZipExtractor extractor;
        try {
          LOG.log(Level.INFO, "Waiting for Java Web Start jar download");
          waitForLoader(15); // TODO: ratio
          waitForLoader(15);
          LOG.log(Level.INFO, "Downloaded build file");
          String zipName = WebStartDownloader.getZipFileName();
          InputStream in =
@@ -1470,7 +1470,6 @@
  private void insureUpgradability() throws ApplicationException {
    BuildInformation currentVersion;
    BuildInformation newVersion;
    try {
      currentVersion = getInstallation().getBuildInformation();
    } catch (ApplicationException e) {
@@ -1488,14 +1487,19 @@
      LOG.log(Level.INFO, "error getting build information for " +
              "staged installation", e);
      throw ApplicationException.createFileSystemException(
              getMsg("error-determining-upgrade-build"), e);    }
    UpgradeOracle uo = new UpgradeOracle(currentVersion, newVersion);
    if (!uo.isSupported()) {
      throw new ApplicationException(ApplicationException.Type.APPLICATION,
              uo.getLocalizedSummaryMessage(), null);
              getMsg("error-determining-upgrade-build"), e);
    }
    UpgradeOracle uo = new UpgradeOracle(
            userInteraction(), currentVersion, newVersion);
    uo.notifyUser();
    if (uo.noServerStartFollowingOperation()) {
      // Some issue dicatates that we don't try and restart the server
      // after this operation.  It may be that the databases are no
      // longer readable after the upgrade or something equally earth
      // shattering.
      getUserData().setStartServer(false);
    }
  }
  private Installation getStagedInstallation()
opends/src/quicksetup/org/opends/quicksetup/upgrader/VersionOracle.java
New file
@@ -0,0 +1,376 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.quicksetup.upgrader;
import org.opends.quicksetup.BuildInformation;
import org.opends.quicksetup.ApplicationException;
import org.opends.quicksetup.UserInteraction;
import org.opends.quicksetup.i18n.ResourceProvider;
import org.opends.server.util.VersionCompatibilityIssue;
import org.opends.server.util.BuildVersion;
import static org.opends.server.util.VersionCompatibilityIssue.*;
import java.util.List;
import java.util.Set;
import java.util.ArrayList;
import java.util.Collections;
import java.util.logging.Logger;
import java.util.logging.Level;
/**
 * This class can answer questions important upgrade/reversion questions
 * like 'can I upgrade from verion X to version Y?' and 'if not then why?'.
 * This is also responsible for obtaining and translating any applicable
 * {@link org.opends.server.util.VersionCompatibilityIssue}s and
 * interacting with the user to inform them of an actions or information
 * that they dictate.
 */
public abstract class VersionOracle {
  static private final Logger LOG =
          Logger.getLogger(VersionOracle.class.getName());
  /** Descriptor for a directive. */
  protected enum DirectiveType {
    /** Causes the tools to refuse to continue. */
    NOT_SUPPORTED,
    /** Causes the tools to display an action dialog. */
    ACTION,
    /** Causes the tools to display an informational dialog. */
    INFO,
    /** Causes the tools to display a warning dialog. */
    WARNING
  }
  /**
   * Holds information that directs tool behavior.
   */
  protected class Directive {
    DirectiveType type;
    String msg;
    /**
     * Creates a parameterized instance.
     *
     * @param type of directive
     * @param localizedMsg for displaying to the user
     */
    public Directive(DirectiveType type, String localizedMsg) {
      this.type = type;
      this.msg = localizedMsg;
    }
    /**
     * Gets the type of issue.
     * @return type of issue
     */
    public DirectiveType getType() {
      return this.type;
    }
    /**
     * Gets the issue's message.
     * @return string message
     */
    public String getMessage() {
      return this.msg;
    }
  }
  /** Used for interacting with the user. */
  protected UserInteraction ui;
  /** Version issues applicable to this operation. */
  protected List<Directive> directives;
  /** Information about the current build version. */
  protected BuildInformation currentBuildInfo;
  /** Information about the proposed new build version. */
  protected BuildInformation newBuildInfo;
  private boolean isSupported = true;
  private boolean noServerStart = false;
  /**
   * Creates a parameterized instance.
   * @param ui for interacting with the user
   * @param current build version
   * @param neu build version
   */
  public VersionOracle(UserInteraction ui,
                       BuildInformation current,
                       BuildInformation neu) {
    this.ui = ui;
    this.currentBuildInfo = current;
    this.newBuildInfo = neu;
    // Get the list of possible version incompatibility events (aka flag days)
    List<VersionCompatibilityIssue> compatibilityIssues;
    Set<Integer> excludeIds = current.getIncompatibilityEventIds();
    if (excludeIds != null) {
      compatibilityIssues = getEvents(excludeIds);
    } else {
      // This method is only used as a fallback for pre 1.0.0 servers which
      // do not advertise incompatible version events.
      LOG.log(Level.INFO, "Legacy method for obtaining compatibility issues");
      BuildVersion bv = new BuildVersion(
              current.getMajorVersion(),
              current.getMinorVersion(),
              current.getPointVersion(),
              current.getRevisionNumber());
      compatibilityIssues = getEvents(bv);
    }
    directives = processEvents(compatibilityIssues);
  }
  /**
   * Interacts with the user to let them know about any
   * version issues applicable to operations between the
   * builds supplied in the constructor.
   *
   * @throws ApplicationException if something goes wrong or
   *         the user cancels the operation.
   */
  public abstract void notifyUser() throws ApplicationException;
  /**
   * Indicates whether or not this operation would be considered an
   * upgrade (as opposed to a reversion).
   *
   * @return boolean where true indicates that this would be an upgrade;
   *         false indicates that this would be a reversion.
   */
  public boolean isUpgrade() {
    return currentBuildInfo.compareTo(newBuildInfo) < 0;
  }
  /**
   * Indicates whether or not this operation would be considered an
   * reversion (as opposed to an upgrade).
   *
   * @return boolean where true indicates that this would be a reversion;
   *         false indicates that this would be an upgrade.
   */
  public boolean isReversion() {
    return currentBuildInfo.compareTo(newBuildInfo) > 0;
  }
  /**
   * Returns whether or not this operation is supported.
   * @return true to indicate this operation is supported; false otherwise
   */
  public boolean isSupported() {
    return isSupported;
  }
  /**
   * Indicates whether the set of version issues dictates that the server
   * not be restarted afterward.
   *
   * @return true meaning the server won't be restarted; false otherwise
   */
  public boolean noServerStartFollowingOperation() {
    return noServerStart;
  }
  /**
   * Gets a list of issues applicable to this operation.
   * @return list of issues
   */
  protected List<Directive> getIssues() {
    return directives;
  }
  /**
   * Indicates whether or not there are issues with this operation.
   * @return true indicating there are issues; false otherwise
   */
  protected boolean hasIssues() {
    return (directives != null && directives.size() > 0);
  }
  /**
   * Given a particular cause return a detail message appropriate
   * for this operation.
   *
   * @param cause of issue
   * @return message for presenting to the user
   */
  protected abstract String getLocalizedDetailMessage(
          VersionCompatibilityIssue.Cause cause);
  /**
   * Given a particular cause indicates whether or not the user
   * will be confronted with verbage explaining that they will
   * have to perform extra actions for this operation.
   *
   * @param cause of issue
   * @return message for presenting to the user
   */
  protected abstract boolean isActionRequired(
          VersionCompatibilityIssue.Cause cause);
  /**
   * Given a particular cause indicates whether or not this
   * operation should be allowed to continue.
   *
   * @param cause of issue
   * @return message for presenting to the user
   */
  protected abstract boolean isUnsupported(
          VersionCompatibilityIssue.Cause cause);
  /**
   * Given a particular cause indicates whether or not this
   * the user will be shown a warning dialog containing
   * a warning message regarding this operation.
   *
   * @param cause of issue
   * @return message for presenting to the user
   */
  protected abstract boolean isWarning(
          VersionCompatibilityIssue.Cause cause);
  /**
   * Given a particular cause indicates whether or not this
   * the user will be shown some verbage that may contain
   * information about this operation.
   *
   * @param cause of issue
   * @return message for presenting to the user
   */
  protected boolean isNotification(Cause cause) {
    boolean isNotification = false;
    if (cause != null) {
      String msg = getLocalizedDetailMessage(cause);
      if (msg != null && !isWarning(cause) && !isActionRequired(cause)) {
        isNotification = true;
      }
    }
    return isNotification;
  }
  /**
   * Gets a list of strings representing the steps neccessary
   * to export and then reimport the data.
   *
   * @return List containing strings representing intruction steps
   */
  protected List<String> getExportImportInstructions() {
    // Creates the steps by accessing messages starting with
    // 'oracle-ei-action-step' starting with 'oracle-ei-action-step1'
    // until there are no more message having
    List<String> instructions = new ArrayList<String>();
    try {
      int i = 1;
      while (true) {
        String m = getMsg("oracle-ei-action-step" + i++);
        if (m != null) {
          instructions.add(m);
        } else {
          break;
        }
      }
    } catch (Exception e) {
      // ignore
    }
    return instructions;
  }
  /**
   * Returns a localized message for a key value.  In  the properties file we
   * have something of type:
   * key=value
   *
   * For instance if we pass as key "mykey" and as arguments {"value1"} and
   * in the properties file we have:
   * mykey=value with argument {0}.
   *
   * This method will return "value with argument value1".
   * @see org.opends.quicksetup.i18n.ResourceProvider#getMsg(String, String[])
   * @param key the key in the properties file.
   * @param args the arguments to be passed to generate the resulting value.
   * @return the value associated to the key in the properties file.
   */
  protected String getMsg(String key, String... args) {
    return ResourceProvider.getInstance().getMsg(key, args);
  }
  /**
   * Converts a set of compatibility issues into a set of set of
   * action oriented issues for directing tool behavior.
   *
   * @param compatibilityIssues list of issues
   * @return list of directives
   */
  private List<Directive> processEvents(
          List<VersionCompatibilityIssue> compatibilityIssues)
  {
    List<Directive> directives = new ArrayList<Directive>();
    if (compatibilityIssues != null) {
      for (VersionCompatibilityIssue evt : compatibilityIssues) {
        VersionCompatibilityIssue.Cause cause = evt.getCause();
        Set<Effect> effects = cause.getEffects();
        String msg = getLocalizedDetailMessage(cause);
        if (isUnsupported(cause)) {
          isSupported = false;
          directives.add(new Directive(DirectiveType.NOT_SUPPORTED, msg));
        } else if (isActionRequired(cause)) {
          directives.add(new Directive(DirectiveType.ACTION, msg));
        } else if (isWarning(cause)) {
          directives.add(new Directive(DirectiveType.WARNING, msg));
        } else if (isNotification(cause)) {
          directives.add(new Directive(DirectiveType.INFO, msg));
        }
        if ((effects.contains(
                Effect.NO_SERVER_RESTART_FOLLOWING_REVERSION) ||
                effects.contains(
                        Effect.REVERSION_DATA_EXPORT_AND_REIMPORT_REQUIRED) ||
                effects.contains(
                        Effect.UPGRADE_DATA_EXPORT_AND_REIMPORT_REQUIRED)))
        {
          noServerStart = true;
        }
      }
    }
    return Collections.unmodifiableList(directives);
  }
}
opends/src/quicksetup/org/opends/quicksetup/util/Utils.java
@@ -1196,7 +1196,8 @@
   * Inserts HTML break tags into <code>d</code> breaking it up
   * so that ideally no line is longer than <code>maxll</code>
   * assuming no single word is longer then <code>maxll</code>.
   * If the string already contains HTML line breaks, they are
   * If the string already contains HTML tags that cause a line
   * break (e.g break and closing list item tags) they are
   * respected by this method when calculating where to place
   * new breaks to control the maximum line length.
   *
@@ -1210,18 +1211,29 @@
    if (len <= 0)
      return d;
    if (len > maxll) {
      int p = d.lastIndexOf(Constants.HTML_LINE_BREAK, maxll);
      if (p > 0 && p < len) {
        return d.substring(0, p + Constants.HTML_LINE_BREAK.length()) +
               breakHtmlString(
                       d.substring(p + Constants.HTML_LINE_BREAK.length()),
                       maxll);
      } else {
        p = d.lastIndexOf(' ', maxll);
        if (p <= 0) {
          p = d.indexOf(' ', maxll);
      // First see if there are any tags that would cause a
      // natural break in the line.  If so start line break
      // point evaluation from that point.
      for (String tag : Constants.BREAKING_TAGS) {
        int p = d.lastIndexOf(tag, maxll);
        if (p > 0 && p < len) {
          return d.substring(0, p + tag.length()) +
                 breakHtmlString(
                         d.substring(p + tag.length()),
                         maxll);
        }
      }
      // Now look for spaces in which to insert a break.
      // First see if there are any spaces counting backward
      // from the max line length.  If there aren't any, then
      // use the first space encountered after the max line
      // lenght.
      int p = d.lastIndexOf(' ', maxll);
      if (p <= 0) {
        p = d.indexOf(' ', maxll);
      }
      if (p > 0 && p < len) {
        return d.substring(0, p) +
                Constants.HTML_LINE_BREAK +
@@ -1235,6 +1247,15 @@
  }
  /**
   * Converts existing HTML break tags to native line sparators.
   * @param s string to convert
   * @return converted string
   */
  static public String convertHtmlBreakToLineSeparator(String s) {
    return s.replaceAll("\\<br\\>", Constants.LINE_SEPARATOR);
  }
  /**
   * Strips any potential HTML markup from a given string.
   * @param s string to strip
   * @return resulting string
opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -84,6 +84,8 @@
import org.opends.server.util.SetupUtils;
import org.opends.server.util.TimeThread;
import org.opends.server.util.Validator;
import org.opends.server.util.VersionCompatibilityIssue;
import org.opends.server.util.StaticUtils;
import org.opends.server.util.args.ArgumentException;
import org.opends.server.util.args.ArgumentParser;
import org.opends.server.util.args.BooleanArgument;
@@ -8817,6 +8819,10 @@
      System.out.println("Build JVM Version:   " + BUILD_JVM_VERSION);
      System.out.println("Build JVM Vendor:    " + BUILD_JVM_VENDOR);
      System.out.println("Upgrade Event IDs:   " +
              StaticUtils.listToString(
                      VersionCompatibilityIssue.getAllEvents(), ","));
      return;
    }
    else if (systemInfo.isPresent())
opends/src/server/org/opends/server/messages/MessageHandler.java
@@ -65,6 +65,7 @@
 *   <LI>00C -- Access Control</LI>
 *   <LI>00D -- Administration framework</LI>
 *   <LI>00E -- Synchronization</LI>
 *   <LI>00E -- Version Compatibility (Flag Days)</LI>
 *   <LI>800 through FFE -- Reserved for third-party modules</LI>
 *   <LI>FFF -- User-defined processing</LI>
 * </UL>
@@ -193,7 +194,10 @@
   */
  public static final int CATEGORY_MASK_SYNC = 0x0E000000;
  /**
   * The category bitmask used for messages associated with flag day messages.
   */
  public static final int CATEGORY_MASK_VERSION_COMPATIBITY_ISSUES = 0x0F000000;
  /**
   * The category bitmask that will be used for messages associated with
@@ -294,6 +298,7 @@
    AdminMessages.registerMessages();
    AciMessages.registerMessages();
    ReplicationMessages.registerMessages();
    VersionMessages.registerMessages();
  }
opends/src/server/org/opends/server/messages/VersionMessages.java
New file
@@ -0,0 +1,151 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.messages;
import static org.opends.server.messages.MessageHandler.registerMessage;
import static org.opends.server.messages.MessageHandler.
        CATEGORY_MASK_VERSION_COMPATIBITY_ISSUES;
import static org.opends.server.messages.MessageHandler.
        SEVERITY_MASK_INFORMATIONAL;
/**
 * Messages relating to incompatible version events (also known as 'flag-days')
 * that might cause potential issues or problems with upgrade or reversions
 * of a particular installation from one version to another.  These messages
 * are usually shown to the user during an upgrade or reversion process to
 * alert them to any postential issues.
 */
public class VersionMessages {
  /**
   * Message detailing possible upgrade issues caused by the upgrade of the
   * Berkley DB libraries in SVN rev 890.
   */
  public static final int MSGID_890_UPGRADE =
       CATEGORY_MASK_VERSION_COMPATIBITY_ISSUES |
               SEVERITY_MASK_INFORMATIONAL | 1;
  /**
   * Message detailing possible reversion issues caused by the upgrade of the
   * Berkley DB libraries in SVN rev 890.
   */
  public static final int MSGID_890_REVERSION =
       CATEGORY_MASK_VERSION_COMPATIBITY_ISSUES |
               SEVERITY_MASK_INFORMATIONAL | 2;
  /**
   * Message detailing possible upgrade issues cause by the database
   * record format change committed with SVN rev 1582.
   */
  public static final int MSGID_1582_UPGRADE =
       CATEGORY_MASK_VERSION_COMPATIBITY_ISSUES |
               SEVERITY_MASK_INFORMATIONAL | 3;
  /**
   * Message detailing possible reversion issues cause by the database
   * record format change committed with SVN rev 1582.
   */
  public static final int MSGID_1582_REVERSION =
       CATEGORY_MASK_VERSION_COMPATIBITY_ISSUES |
               SEVERITY_MASK_INFORMATIONAL | 4;
  /**
   * Message detailing possible reversion issues cause by the database
   * record format change committed with SVN rev 2049.
   */
  public static final int MSGID_2049_UPGRADE =
       CATEGORY_MASK_VERSION_COMPATIBITY_ISSUES |
               SEVERITY_MASK_INFORMATIONAL | 5;
  /**
   * Message detailing possible reversion issues cause by the database
   * record format change committed with SVN rev 2049.
   */
  public static final int MSGID_2049_REVERSION =
       CATEGORY_MASK_VERSION_COMPATIBITY_ISSUES |
               SEVERITY_MASK_INFORMATIONAL | 6;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
  public static void registerMessages() {
    registerMessage(MSGID_890_UPGRADE,
            "With this upgrade, the Berkley DB Java Edition JAR " +
                    "will be upgraded to version 3.2.13 which introduces " +
                    "incompatibilities to the data format.  Consequently " +
                    "if at a later time you wish to revert this installation " +
                    "to its prior version you will have to export the data " +
                    "from this server and reimport it once the reversion " +
                    "has finished");
    registerMessage(MSGID_890_REVERSION,
            "With this reversion, the Berkley DB Java Editiong JAR " +
                    "will be downgraded to an older version which uses a " +
                    "different data format than the current version." +
                    "In order to revert this server you will have to export " +
                    "the data from this server and reimport it after the " +
                    "reversion has finished");
    registerMessage(MSGID_1582_UPGRADE,
            "This upgrade introduces improvements to the data format " +
                    "which are not backward compatible with the current " +
                    "version.  Consequently " +
                    "if at a later time you wish to revert this installation " +
                    "to its prior version you will have to export the data " +
                    "from this server and reimport it once the reversion " +
                    "has finished");
    registerMessage(MSGID_1582_REVERSION,
            "With this reversion the data format used to store data by the " +
                    "server will be reverted to a prior version.  " +
                    "In order to revert this server you will have to export " +
                    "the data from this server and reimport it after the " +
                    "reversion has finished");
    registerMessage(MSGID_2049_UPGRADE,
            "This upgrade introduces improvements to the data format " +
                    "which are not backward compatible with the current " +
                    "version.  Consequently " +
                    "if at a later time you wish to revert this installation " +
                    "to its prior version you will have to export the data " +
                    "from this server and reimport it once the reversion " +
                    "has finished");
    registerMessage(MSGID_2049_REVERSION,
            "With this reversion the data format used to store data by the " +
                    "server will be reverted to a prior version.  " +
                    "In order to revert this server you will have to export " +
                    "the data from this server and reimport it after the " +
                    "reversion has finished");
  }
}
opends/src/server/org/opends/server/util/BuildVersion.java
New file
@@ -0,0 +1,131 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.util;
/**
 * Represents a particular version of OpenDS useful for making
 * comparisons between versions.
 */
public class BuildVersion implements Comparable<BuildVersion> {
  /** Major release number. */
  int major;
  /** Minor release number. */
  int minor;
  /** Point release number. */
  int point;
  /** Subversion revision number. */
  long rev;
  /**
   * Creates a new instance using current build data.
   *
   * @return BuildVersion representing current data
   */
  static public BuildVersion getCurrent() {
    return new BuildVersion(
            DynamicConstants.MAJOR_VERSION,
            DynamicConstants.MINOR_VERSION,
            DynamicConstants.POINT_VERSION,
            DynamicConstants.REVISION_NUMBER);
  }
  /**
   * Constructs an instance from build data.
   * @param major release number
   * @param minor release number
   * @param point release number
   * @param rev Subversion revision number
   */
  public BuildVersion(int major, int minor, int point, long rev) {
    this.major = major;
    this.minor = minor;
    this.point = point;
    this.rev = rev;
  }
  /**
   * Gets the major release number.
   * @return int major release number
   */
  public int getMajorVersion() {
    return major;
  }
  /**
   * Gets the minor release number.
   * @return int minor release number
   */
  public int getMinorVersion() {
    return minor;
  }
  /**
   * Gets the point release number.
   * @return int point release number
   */
  public int getPointVersion() {
    return point;
  }
  /**
   * Gets the Subversion revision number.
   * @return long Subversion revision number
   */
  public long getRevisionNumber() {
    return rev;
  }
  /**
   * {@inheritDoc}
   */
  public int compareTo(BuildVersion version) {
    if (major == version.major) {
      if (minor == version.minor) {
        if (point == version.point) {
          if (rev == version.rev) {
            return 0;
          } else if (rev < version.rev) {
            return -1;
          }
        } else if (point < version.point) {
          return -1;
        }
      } else if (minor < version.minor) {
        return -1;
      }
    } else if (major < version.major) {
      return -1;
    }
    return 1;
  }
}
opends/src/server/org/opends/server/util/StaticUtils.java
@@ -3256,7 +3256,26 @@
    return stringArray;
  }
  /**
   * Creates a string representation of the elements in the
   * <code>list</code> separated by <code>separator</code>.
   *
   * @param list the list to print
   * @param separator to use between elements
   *
   * @return String representing the list
   */
  static public String listToString(List<?> list, String separator)
  {
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < list.size(); i++) {
      sb.append(list.get(i));
      if (i < list.size() - 1) {
        sb.append(separator);
      }
    }
    return sb.toString();
  }
  /**
   * Retrieves an array list containing the contents of the provided array.
opends/src/server/org/opends/server/util/VersionCompatibilityIssue.java
New file
@@ -0,0 +1,530 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.util;
import static org.opends.server.messages.MessageHandler.getMessage;
import static org.opends.server.messages.VersionMessages.*;
import java.util.Set;
import java.util.List;
import java.util.Collections;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Comparator;
import java.util.Collection;
/**
 * Record for version compatibility issues (also known as 'flag days') which
 * are events associated with particular builds or builds between which upgrade
 * or reversion may required additional steps, notification of issues, or
 * be prohibitted altogether.
 */
public class VersionCompatibilityIssue {
  //***************************************************
  //
  //  TO DEFINE A NEW ISSUE:
  //
  //  Step 1:  Select (or add to) effects from the list
  //           below that will cause the upgrade or
  //           reversion tools to behave in particular
  //           ways.  If you add to this list you will
  //           likely need to update the UpgradeOracle
  //           and ReversionOracle code.
  //
  //  Step 2:  [scroll down]...
  //
  //***************************************************
  /**
   * Effects cause the upgrade and revision tools to behave
   * in specific ways in response to compatibility issues.
   */
  public enum Effect {
    /**
     * Before a reversion can take place there must be a complete
     * data export to LDIF followed by a complete data import after
     * the operation has completed.  Assigning this effect to an
     * issue will cause a detailed set of instructions to appear in
     * the reversion tool explaining how to perform the task.
     */
    REVERSION_DATA_EXPORT_AND_REIMPORT_REQUIRED,
    /**
     * Before an upgrade can take place there must be a complete
     * data export to LDIF followed by a complete data import after
     * the operation has completed.  Assigning this effect to an
     * issue will cause a detailed set of instructions to appear in
     * the upgrade tool explaining how to perform the task.
     */
    UPGRADE_DATA_EXPORT_AND_REIMPORT_REQUIRED,
    /**
     * Indicates that the upgrader will show an informational message to the
     * administrator.  Use this effect when you want to have the
     * upgrader show the user an informational message during upgrade
     * but the message does not dictate that an action be performed.
     * For instance you might want to let the user know that due to
     * a data format incompatibility, it will be more difficult to
     * revert this build to its previous version following this upgrade.
     *
     * If you want the message to be scarier, use
     * <code>UPGRADE_SHOW_WARNING_MESSAGE</code> instead.
     */
    UPGRADE_SHOW_INFO_MESSAGE,
    /**
     * Indicates that the reverter tool will show a message to the
     * administrator.  Use this effect when you want to have the
     * reverter show the user an informational message during upgrade
     * but the message does not dictate that an action be performed.
     *
     * If you want the message to be scarier, use
     * <code>REVERSION_SHOW_WARNING_MESSAGE</code> instead.
     */
    REVERSION_SHOW_INFO_MESSAGE,
    /**
     * Indicates that the upgrader will show a message to the
     * administrator.  Use this effect when you want to have the
     * upgrader show the user an informational message during upgrade
     * but the message does not dictate that an action be performed.
     * For instance you might want to let the user know that due to
     * a data format incompatibility, it will be more difficult to
     * revert this build to its previous version following this upgrade.
     *
     * If you want the message to be less scary, use
     * <code>UPGRADE_SHOW_INFO_MESSAGE</code> instead.
     */
    UPGRADE_SHOW_WARNING_MESSAGE,
    /**
     * Indicates that the reverter tool will show a message to the
     * administrator.  Use this effect when you want to have the
     * reverter show the user an informational message during upgrade
     * but the message does not dictate that an action be performed.
     *
     * If you want the message to be less scary, use
     * <code>REVERSION_SHOW_INFO_MESSAGE</code> instead.
     */
    REVERSION_SHOW_WARNING_MESSAGE,
    /**
     * Indicates that the user needs to perform some manual action
     * (for which there is not effect currently defined such as
     * <code>UPGRADE_DATA_EXPORT_AND_REIMPORT_REQUIRED</code>) in order for
     * the operation to be successful.  The action itself should
     * be described in detail in the upgrade message.
     */
    UPGRADE_MANUAL_ACTION_REQUIRED,
    /**
     * Indicates that the user needs to perform some manual action
     * (for which there is not effect currently defined such as
     * <code>REVERSION_DATA_EXPORT_AND_REIMPORT_REQUIRED</code>) in order for
     * the operation to be successful.  The action itself should
     * be described in detail in the reversion message.
     */
    REVERSION_MANUAL_ACTION_REQUIRED,
    /**
     * Indicates that it is not possible to upgrade between to builds
     * between which lies a flag day.  The upgrader will refuse to
     * operate in this case.
     */
    UPGRADE_NOT_POSSIBLE,
    /**
     * Indicates that it is not possible to revert between to builds
     * between which lies a flag day.  The reverter will refuse to run
     * in this case.
     */
    REVERSION_NOT_POSSIBLE,
    /**
     * Indicates that for some reason the server should not be restarted
     * following a reversion.  There might be situations where the admin
     * needs to perform some actions before the server restarts (such as
     * the database format being incompatible and the data needing an
     * export followed by a reimport).  This effect need not be included
     * with <code>UPGRADE_DATA_EXPORT_AND_REIMPORT_REQUIRED</code> and
     * <code>REVERSION_DATA_EXPORT_AND_REIMPORT_REQUIRED</code> as this
     * is assumed.
     */
    NO_SERVER_RESTART_FOLLOWING_REVERSION,
  }
  //***************************************************
  //
  //  TO DEFINE A NEW ISSUE:
  //
  // STEP 1:  [scroll up]
  //
  // STEP 2:  Define an cause below.  A cause must be a specific
  //          event.  For instance 'upgrade of the database libraries'
  //          on 12/17/2006.  A cause associates the effect you selected
  //          in Step 1, detailed reversion and/or upgrade messages and
  //          a unique ID.
  //
  //          A single issue may be apply to multiple branches of the
  //          codebase.  For instance a single event might cause a flag
  //          day between upgrade/reversions from 1.0 to 2.0 as well as
  //          upgrading from 1.0 to 1.1.  Therefore you must make sure
  //          that causes that appear in multiple branches have the same
  //          ID.  Also, IDs should be unique among all causes in the
  //          codebase.
  //
  // STEP 3:  [scroll down]
  //
  //***************************************************
  /**
   * Unique descriptor of an event that created a flag day for one
   * or more versions of the OpenDS codebase.
   */
  public enum Cause {
    /**
     * Database format change committed on 6/7/2007
     * and described in the SVN log for rev 2049.
     */
    DB_FORMAT_CHANGE_2(
            3, // Unique ID.  See javadoc for more information.
            getMessage(MSGID_2049_UPGRADE),
            getMessage(MSGID_2049_REVERSION),
            Effect.REVERSION_DATA_EXPORT_AND_REIMPORT_REQUIRED,
            Effect.UPGRADE_SHOW_WARNING_MESSAGE),
    /**
     * Database format change committed on 4/6/2007
     * and described in the SVN log for rev 1582.
     */
    DB_FORMAT_CHANGE_1(
            2,  // Unique ID.  See javadoc for more information.
            getMessage(MSGID_1582_UPGRADE),
            getMessage(MSGID_1582_REVERSION),
            Effect.REVERSION_DATA_EXPORT_AND_REIMPORT_REQUIRED,
            Effect.UPGRADE_SHOW_WARNING_MESSAGE),
    /**
     * Upgrade of Berkley DB library to 3.2.13 on
     * 12/17/2006.
     */
    BERKLEY_UPGRADE_1(
            1,  // Unique ID.  See javadoc for more information.
            getMessage(MSGID_890_UPGRADE),
            getMessage(MSGID_890_REVERSION),
            Effect.REVERSION_DATA_EXPORT_AND_REIMPORT_REQUIRED,
            Effect.UPGRADE_SHOW_WARNING_MESSAGE);
    /**
     * Gets a <code>Cause</code> from its unique ID.  If no cause
     * is associated with <code>id</code> this method returns null.
     * @param id of a cause
     * @return Cause with <code>id</code>
     */
    static Cause fromId(int id) {
      Cause cause = null;
      EnumSet<Cause> es = EnumSet.allOf(Cause.class);
      for (Cause c : es) {
        if (c.getId() == id) {
          cause = c;
          break;
        }
      }
      return cause;
    }
    private int id;
    private Set<Effect> effects = new HashSet<Effect>();
    private String upgradeMsg;
    private String reversionMsg;
    /**
     * Creates a parameterized instance.
     *
     * @param id of this cause.  It would get very complicated to try to
     *        deal with releases as a graph and attempting to compare
     *        versions to see what issues apply during an upgrade/reversion
     *        between two releases.  Therefore IDs are used by the tools
     *        to identify issues would have already been seen during a previous
     *        upgrade and do not need to be rehashed.
     *        <p>
     *        So if an issue exists in the 1.0 branch, an upgrade from 2.0
     *        to 3.0 will suppress the issue since it would presumably already
     *        been dealt with when 2.0 was installed or upgraded to.  Likewise
     *        if an issue is assocated with a particular minor version (1.1 for
     *        instance) major upgrades (1.0 to 2.0) will avoid presenting the
     *        issue.
     *
     *        <ol>
     *        <li>IDs must be unique among different causes in all branches
     *        of the OpenDS code.</li>
     *
     *        <li>Causes in different branches representing the same issue
     *        must have identical IDs.</li>
     *
     *        <li>The IDs are advertised by the server when start-ds -F
     *        is invoked.  Therefore they should be kept to as few
     *        characters as possible.</li>
     *        </ol>
     *
     * @param upgradeMessage a message to be shown to the user during an
     *        upgrade between two different version between which this issue
     *        lies.  This message might detail instructions for manual actions
     *        that must be performed (when used with the
     *        <code>UPGRADE_MANUAL_ACTION_REQUIRED</code>) or give the
     *        user a warning message (when used with
     *        <code>UPGRADE_SHOW_WARNING_MESSAGE</code>).  If a message is
     *        present but no effects that would dictate how message is to
     *        be presented <code>UPGRADE_SHOW_INFO_MESSAGE</code> is
     *        assumed.  This parameter may also be null in which case no
     *        action will be taken during upgrade.
     *
     * @param reversionMessage a message to be shown to the user during a
     *        reversion between two different version between which this issue
     *        lies.  This message might detail instructions for manual actions
     *        that must be performed (when used with the
     *        <code>REVERSION_MANUAL_ACTION_REQUIRED</code>) or give the
     *        user a warning message (when used with
     *        <code>REVERSION_SHOW_WARNING_MESSAGE</code>).  If a message is
     *        present but no effects that would dictate how message is to
     *        be presented <code>REVERSION_SHOW_INFO_MESSAGE</code> is
     *        assumed.  This parameter may also be null in which case no
     *        action will be taken during reversion.
     *
     * @param effects of this cause which cause the upgrade/reversion tools
     *        to behave in particular ways
     */
    private Cause(int id, String upgradeMessage, String reversionMessage,
          Effect... effects) {
      this.id = id;
      this.upgradeMsg = upgradeMessage;
      this.reversionMsg = reversionMessage;
      if (effects != null) {
        for (Effect c : effects) {
          this.effects.add(c);
        }
      }
    }
    /**
     * Gets the ID of this cause.
     * @return id of this cause
     */
    public int getId() {
      return this.id;
    }
    /**
     * Gets the set of effects that cause the upgrade/reversion
     * tools to behave in particular ways.
     *
     * @return set of effects
     */
    public Set<Effect> getEffects() {
      return Collections.unmodifiableSet(effects);
    }
    /**
     * Gets a localized message to be shown to the user during
     * the upgrade process.
     *
     * @return a message to be shown to the user during an
     *         upgrade between two different version between which this issue
     *         lies.  This message might detail instructions for manual actions
     *         that must be performed (when used with the
     *         <code>UPGRADE_MANUAL_ACTION_REQUIRED</code>) or just give the
     *         user useful information (when used with
     *         <code>UPGRADE_SHOW_INFO_MESSAGE</code>)
     */
    public String getLocalizedUpgradeMessage() {
      return upgradeMsg;
    }
    /**
     * Gets a localized message to be shown to the user during
     * the reversion process.
     *
     * @return a message to be shown to the user during an
     *         upgrade between two different version between which this issue
     *         lies.  This message might detail instructions for manual actions
     *         that must be performed (when used with the
     *         <code>REVERSION_MANUAL_ACTION_REQUIRED</code>) or just give the
     *         user useful information (when used with
     *         <code>REVERSION_SHOW_INFO_MESSAGE</code>)
     */
    public String getLocalizedReversionMessage() {
      return reversionMsg;
    }
  }
  /**
   * Container for registered issues.
   */
  static private final Set<VersionCompatibilityIssue>
          VERSION_COMPATIBILITY_ISSUES =
          new HashSet<VersionCompatibilityIssue>();
  //***************************************************
  //
  //  TO DEFINE A NEW ISSUE:
  //
  // STEP 2:  [scroll up]
  //
  // STEP 3:  Associate the cause with a particular build.
  //
  // DONE
  //
  //***************************************************
  static {
    register(Cause.DB_FORMAT_CHANGE_2, new BuildVersion(0, 9, 0, 2049));
    register(Cause.DB_FORMAT_CHANGE_1, new BuildVersion(0, 1, 0, 1582));
    register(Cause.BERKLEY_UPGRADE_1, new BuildVersion(0, 1, 0, 890));
  }
  static private void register(Cause cause,
                               BuildVersion version) {
    VERSION_COMPATIBILITY_ISSUES.add(new VersionCompatibilityIssue(cause,
            version));
  }
  /**
   * Gets the list of all registered issues.
   *
   * @return list of issues sorted by build version in which
   *         they appear
   */
  static public List<VersionCompatibilityIssue> getAllEvents() {
    List<VersionCompatibilityIssue> issueList =
            new ArrayList<VersionCompatibilityIssue>
                    (VERSION_COMPATIBILITY_ISSUES);
    Collections.sort(issueList, VERSION_COMPARATOR);
    return Collections.unmodifiableList(issueList);
  }
  /**
   * Gets the list of all registered issues excluding the
   * issues specified by <code>excludeIds</code>.
   *
   * @param excludeIds collection of IDs representing issues
   *        that will not be returned in the list
   *
   * @return list of issues sorted by build version in which
   *         they appear
   */
  static public List<VersionCompatibilityIssue> getEvents(
          Collection<Integer> excludeIds)
  {
    if (excludeIds == null) excludeIds = Collections.emptySet();
    List<VersionCompatibilityIssue> issueList =
            new ArrayList<VersionCompatibilityIssue>();
    for (VersionCompatibilityIssue evt : VERSION_COMPATIBILITY_ISSUES) {
      if (!excludeIds.contains(evt.getCause().getId())) {
        issueList.add(evt);
      }
    }
    Collections.sort(issueList, VERSION_COMPARATOR);
    return Collections.unmodifiableList(issueList);
  }
  /**
   * Returns events that have happened in between the SVN revision numbers
   * of two different builds.  Note that this method does not necessarily
   * return all events that are pertinent.  For instance a partilar event
   * may have happend in a branch that we don't care about for the current
   * upgrade.  So this method should really just be used as a fall-back
   * in the case where we are upgrading/reverting a build that was not
   * instrumented to return the Upgrade Event IDs using start-ds -F.
   *
   * @param from build from which events will be returned
   * @return List or IncompatibleVersionEvent objects
   */
  static public List<VersionCompatibilityIssue> getEvents(BuildVersion from) {
    List<VersionCompatibilityIssue> issueList =
            new ArrayList<VersionCompatibilityIssue>();
    for (VersionCompatibilityIssue evt : VERSION_COMPATIBILITY_ISSUES) {
      BuildVersion evtVer = evt.getVersion();
      if (evtVer.compareTo(from) >= 0) {
        issueList.add(evt);
      }
    }
    Collections.sort(issueList, VERSION_COMPARATOR);
    return issueList;
  }
  /**
   * Comparator used to sort issues by the build version for
   * which they apply.
   */
  static private final Comparator<VersionCompatibilityIssue>
          VERSION_COMPARATOR = new Comparator<VersionCompatibilityIssue>()
  {
    public int compare(VersionCompatibilityIssue o1,
                       VersionCompatibilityIssue o2) {
      return o1.getVersion().compareTo(o2.getVersion());
    }
  };
  private Cause cause;
  private BuildVersion version;
  private VersionCompatibilityIssue(Cause cause, BuildVersion version) {
    this.cause = cause;
    this.version = version;
  }
  /**
   * Gets the cause of this issue.
   * @return the cause
   */
  public Cause getCause() {
    return this.cause;
  }
  /**
   * Gets the build version for which this issue applies.
   * @return build version
   */
  public BuildVersion getVersion() {
    return this.version;
  }
  /**
   * {@inheritDoc}
   */
  public String toString() {
    return Integer.toString(cause.getId());
  }
}