001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Portions Copyright 2013-2015 ForgeRock AS
025 */
026package org.opends.server.tools.upgrade;
027
028import java.io.File;
029import java.io.FileWriter;
030import java.io.IOException;
031import java.util.Arrays;
032import java.util.LinkedList;
033import java.util.List;
034import java.util.NavigableMap;
035import java.util.TreeMap;
036
037import javax.security.auth.callback.ConfirmationCallback;
038
039import org.forgerock.i18n.LocalizableMessage;
040import org.forgerock.i18n.slf4j.LocalizedLogger;
041import org.opends.server.core.LockFileManager;
042import org.opends.server.util.BuildVersion;
043import org.opends.server.util.StaticUtils;
044
045import com.forgerock.opendj.cli.ClientException;
046import com.forgerock.opendj.cli.ReturnCode;
047
048import static com.forgerock.opendj.cli.Utils.*;
049import static javax.security.auth.callback.TextOutputCallback.*;
050import static org.opends.messages.ToolMessages.*;
051import static org.opends.server.tools.upgrade.FormattedNotificationCallback.*;
052import static org.opends.server.tools.upgrade.LicenseFile.*;
053import static org.opends.server.tools.upgrade.UpgradeTasks.*;
054import static org.opends.server.tools.upgrade.UpgradeUtils.batDirectory;
055import static org.opends.server.tools.upgrade.UpgradeUtils.binDirectory;
056
057/**
058 * This class contains the table of upgrade tasks that need performing when
059 * upgrading from one version to another.
060 */
061public final class Upgrade
062{
063  /** Upgrade's logger. */
064  private static LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
065
066  /** Upgrade supports version from 2.4.5. */
067  private static BuildVersion UPGRADESUPPORTSVERSIONFROM = BuildVersion.valueOf("2.4.5.0000");
068
069  /** The success exit code value. */
070  static final int EXIT_CODE_SUCCESS = 0;
071  /** The error exit code value. */
072  static final int EXIT_CODE_ERROR = 1;
073
074  /**
075   * The exit code value that will be used if upgrade requires manual
076   * intervention.
077   */
078  static final int EXIT_CODE_MANUAL_INTERVENTION = 2;
079
080  /** If the upgrade contains some post upgrade tasks to do. */
081  static boolean hasPostUpgradeTask;
082
083  /** Developers should register upgrade tasks below. */
084  private static final NavigableMap<BuildVersion, List<UpgradeTask>> TASKS = new TreeMap<>();
085  private static final List<UpgradeTask> MANDATORY_TASKS = new LinkedList<>();
086
087  static
088  {
089    // @formatter:off
090    register("2.5.0.6869",
091        modifyConfigEntry(INFO_UPGRADE_TASK_6869_SUMMARY.get(),
092        "(objectClass=ds-cfg-collation-matching-rule)",
093        "add: ds-cfg-collation",
094        "ds-cfg-collation: de:1.3.6.1.4.1.42.2.27.9.4.28.1",
095        "ds-cfg-collation: de-DE:1.3.6.1.4.1.42.2.27.9.4.28.1",
096        "-",
097        "delete: ds-cfg-collation",
098        "ds-cfg-collation: de:1.3.6.1.4.1.142.2.27.9.4.28.1",
099        "ds-cfg-collation: de-DE:1.3.6.1.4.1.142.2.27.9.4.28.1"));
100
101    register("2.5.0.7192",
102        modifyConfigEntry(INFO_UPGRADE_TASK_7192_SUMMARY.get(),
103        "(objectClass=ds-cfg-password-policy)",
104        "add: objectClass",
105        "objectClass: ds-cfg-authentication-policy",
106        "-",
107        "add: ds-cfg-java-class",
108        "ds-cfg-java-class: org.opends.server.core.PasswordPolicyFactory"));
109
110    register("2.5.0.7364",
111        modifyConfigEntry(INFO_UPGRADE_TASK_7364_SUMMARY.get(),
112        "(ds-cfg-java-class=org.opends.server.loggers.TextAuditLogPublisher)",
113        "add: objectClass",
114        "objectClass: ds-cfg-file-based-audit-log-publisher",
115        "-",
116        "delete: objectClass",
117        "objectClass: ds-cfg-file-based-access-log-publisher"));
118
119    register("2.5.0.7466",
120        renameSnmpSecurityConfig(INFO_UPGRADE_TASK_7466_SUMMARY.get()));
121
122    register("2.5.0.7748",
123        newAttributeTypes(INFO_UPGRADE_TASK_7748_1_SUMMARY.get(),
124        "00-core.ldif", "1.3.6.1.4.1.36733.2.1.1.59"), //etag
125        addConfigEntry(INFO_UPGRADE_TASK_7748_2_SUMMARY.get(),
126        "dn: cn=etag,cn=Virtual Attributes,cn=config",
127        "changetype: add",
128        "objectClass: top",
129        "objectClass: ds-cfg-virtual-attribute",
130        "objectClass: ds-cfg-entity-tag-virtual-attribute",
131        "cn: etag",
132        "ds-cfg-java-class: org.opends.server.extensions."
133            + "EntityTagVirtualAttributeProvider",
134        "ds-cfg-enabled: true",
135        "ds-cfg-attribute-type: etag",
136        "ds-cfg-conflict-behavior: real-overrides-virtual",
137        "ds-cfg-checksum-algorithm: adler-32",
138        "ds-cfg-excluded-attribute: ds-sync-hist"));
139
140    register("2.5.0.7834",
141        addConfigEntry(INFO_UPGRADE_TASK_7834_SUMMARY.get(),
142        "dn: cn=Password Expiration Time,cn=Virtual Attributes,cn=config",
143        "changetype: add",
144        "objectClass: top",
145        "objectClass: ds-cfg-virtual-attribute",
146        "objectClass: ds-cfg-password-expiration-time-virtual-attribute",
147        "cn: Password Expiration Time",
148        "ds-cfg-java-class: org.opends.server.extensions."
149            + "PasswordExpirationTimeVirtualAttributeProvider",
150        "ds-cfg-enabled: true",
151        "ds-cfg-attribute-type: ds-pwp-password-expiration-time",
152        "ds-cfg-conflict-behavior: virtual-overrides-real"));
153
154    register("2.5.0.7979",
155        modifyConfigEntry(INFO_UPGRADE_TASK_7979_SUMMARY.get(),
156        "(ds-cfg-java-class=org.opends.server.schema.CertificateSyntax)",
157        "add: objectClass",
158        "objectClass: ds-cfg-certificate-attribute-syntax",
159        "-",
160        "add: ds-cfg-strict-format",
161        "ds-cfg-strict-format: false"));
162
163    register("2.5.0.8124",
164        modifyConfigEntry(INFO_UPGRADE_TASK_8124_SUMMARY.get(),
165        "(ds-cfg-java-class=org.opends.server.schema.JPEGSyntax)",
166        "add: objectClass",
167        "objectClass: ds-cfg-jpeg-attribute-syntax",
168        "-",
169        "add: ds-cfg-strict-format",
170        "ds-cfg-strict-format: false"));
171
172    register("2.5.0.8133",
173        modifyConfigEntry(INFO_UPGRADE_TASK_8133_SUMMARY.get(),
174        "(ds-cfg-java-class=org.opends.server.schema.CountryStringSyntax)",
175        "add: objectClass",
176        "objectClass: ds-cfg-country-string-attribute-syntax",
177        "-",
178        "add: ds-cfg-strict-format",
179        "ds-cfg-strict-format: false"));
180
181    register("2.5.0.8214",
182        requireConfirmation(INFO_UPGRADE_TASK_8214_DESCRIPTION.get(),
183            modifyConfigEntry(INFO_UPGRADE_TASK_8214_SUMMARY.get(),
184                "(ds-cfg-java-class=org.opends.server.extensions.IsMemberOfVirtualAttributeProvider)",
185                "add: ds-cfg-filter",
186                "ds-cfg-filter: (|(objectClass=person)(objectClass=groupOfNames)"
187                    + "(objectClass=groupOfUniqueNames)(objectClass=groupOfEntries))",
188                "-",
189                "delete: ds-cfg-filter",
190                "ds-cfg-filter: (objectClass=person)")));
191
192    register("2.5.0.8387",
193        modifyConfigEntry(INFO_UPGRADE_TASK_8387_SUMMARY.get(),
194        "(objectClass=ds-cfg-dictionary-password-validator)",
195        "add: ds-cfg-check-substrings",
196        "ds-cfg-check-substrings: false"));
197
198    register("2.5.0.8389",
199        modifyConfigEntry(INFO_UPGRADE_TASK_8389_SUMMARY.get(),
200        "(objectClass=ds-cfg-attribute-value-password-validator)",
201        "add: ds-cfg-check-substrings",
202        "ds-cfg-check-substrings: false"));
203
204    register("2.5.0.8487",
205        addConfigEntry(INFO_UPGRADE_TASK_8487_SUMMARY.get(),
206        "dn: cn=PBKDF2,cn=Password Storage Schemes,cn=config",
207        "changetype: add",
208        "objectClass: top",
209        "objectClass: ds-cfg-password-storage-scheme",
210        "objectClass: ds-cfg-pbkdf2-password-storage-scheme",
211        "cn: PBKDF2",
212        "ds-cfg-java-class: org.opends.server.extensions."
213            + "PBKDF2PasswordStorageScheme",
214        "ds-cfg-enabled: true"));
215
216    register("2.5.0.8613",
217        addConfigFile("http-config.json"),
218        addConfigEntry(INFO_UPGRADE_TASK_8613_SUMMARY.get(),
219        "dn: cn=HTTP Connection Handler,cn=Connection Handlers,cn=config",
220        "changetype: add",
221        "objectClass: ds-cfg-http-connection-handler",
222        "objectClass: ds-cfg-connection-handler",
223        "objectClass: top",
224        "ds-cfg-listen-port: 8080",
225        "cn: HTTP Connection Handler",
226        "ds-cfg-max-blocked-write-time-limit: 2 minutes",
227        "ds-cfg-ssl-client-auth-policy: optional",
228        "ds-cfg-use-tcp-keep-alive: true",
229        "ds-cfg-max-request-size: 5 megabytes",
230        "ds-cfg-use-tcp-no-delay: true",
231        "ds-cfg-allow-tcp-reuse-address: true",
232        "ds-cfg-accept-backlog: 128",
233        "ds-cfg-authentication-required: true",
234        "ds-cfg-buffer-size: 4096 bytes",
235        "ds-cfg-config-file: config/http-config.json",
236        "ds-cfg-listen-address: 0.0.0.0",
237        "ds-cfg-java-class: " +
238          "org.opends.server.protocols.http.HTTPConnectionHandler",
239        "ds-cfg-keep-stats: true",
240        "ds-cfg-ssl-cert-nickname: server-cert",
241        "ds-cfg-use-ssl: false",
242        "ds-cfg-enabled: false"));
243
244    register("2.5.0.8832", addConfigEntry(INFO_UPGRADE_TASK_8832_SUMMARY.get(),
245        "dn: cn=File-Based HTTP Access Logger,cn=Loggers,cn=config",
246        "changetype: add",
247        "objectClass: ds-cfg-file-based-http-access-log-publisher",
248        "objectClass: top",
249        "objectClass: ds-cfg-http-access-log-publisher",
250        "objectClass: ds-cfg-log-publisher",
251        "cn: File-Based HTTP Access Logger",
252        "ds-cfg-java-class: " +
253          "org.opends.server.loggers.TextHTTPAccessLogPublisher",
254        "ds-cfg-asynchronous: true",
255        "ds-cfg-log-file: logs/http-access",
256        "ds-cfg-rotation-policy: " +
257          "cn=24 Hours Time Limit Rotation Policy," +
258          "cn=Log Rotation Policies,cn=config",
259        "ds-cfg-rotation-policy: " +
260          "cn=Size Limit Rotation Policy,cn=Log Rotation Policies,cn=config",
261        "ds-cfg-retention-policy: " +
262          "cn=File Count Retention Policy,cn=Log Retention Policies,cn=config",
263        "ds-cfg-log-file-permissions: 640",
264        "ds-cfg-enabled: false"));
265
266    register("2.5.0.8985",
267        newAttributeTypes(INFO_UPGRADE_TASK_8985_1_SUMMARY.get(),
268        "00-core.ldif", "1.2.840.113549.1.9.1"), // emailAddress
269        modifyConfigEntry(INFO_UPGRADE_TASK_8985_2_SUMMARY.get(),
270        "&(ds-cfg-java-class=org.opends.server.extensions." +
271        "SubjectAttributeToUserAttributeCertificateMapper)" +
272        "(ds-cfg-subject-attribute-mapping=e:mail)",
273        "delete:ds-cfg-subject-attribute-mapping",
274        "ds-cfg-subject-attribute-mapping: e:mail",
275        "-",
276        "add:ds-cfg-subject-attribute-mapping",
277        "ds-cfg-subject-attribute-mapping: emailAddress:mail"));
278
279    /** See OPENDJ-992 */
280    register("2.5.0.9013",
281        regressionInVersion("2.5.0.7640",
282            rebuildSingleIndex(INFO_UPGRADE_TASK_9013_DESCRIPTION.get(),
283                "ds-sync-hist")));
284
285    /** See OPENDJ-1284 */
286    register("2.7.0.10133", // userCertificate OID / cACertificate OID
287        newAttributeTypes(INFO_UPGRADE_TASK_10133_1_SUMMARY.get(),
288        "00-core.ldif", "2.5.4.36", "2.5.4.37"),
289        addConfigEntry(INFO_UPGRADE_TASK_10133_2_SUMMARY.get(),
290        "dn: cn=Certificate Exact Matching Rule,cn=Matching Rules,cn=config",
291        "changetype: add",
292        "objectClass: top",
293        "objectClass: ds-cfg-matching-rule",
294        "objectClass: ds-cfg-equality-matching-rule",
295        "cn: Certificate Exact Matching Rule",
296        "ds-cfg-java-class: "
297            + "org.opends.server.schema.CertificateExactMatchingRuleFactory",
298        "ds-cfg-enabled: true"));
299
300
301    /** See OPENDJ-1295 */
302    register("2.7.0.10215",
303        copySchemaFile("03-pwpolicyextension.ldif"));
304
305    register("2.8.0.10214",
306        modifyConfigEntry(INFO_UPGRADE_TASK_10214_SUMMARY.get(),
307          "(ds-cfg-java-class=org.opends.server.loggers.debug.TextDebugLogPublisher)",
308          "delete:ds-cfg-java-class",
309          "-",
310          "add:ds-cfg-java-class",
311          "ds-cfg-java-class: org.opends.server.loggers.TextDebugLogPublisher"));
312
313    register("2.8.0.10232",
314        modifyConfigEntry(INFO_UPGRADE_TASK_10232_SUMMARY.get(),
315          "(objectclass=ds-cfg-file-based-debug-log-publisher)",
316          "delete:ds-cfg-default-debug-level"));
317
318    register("2.8.0.10329",
319        modifyConfigEntry(INFO_UPGRADE_TASK_10329_SUMMARY.get(),
320            "&(objectclass=ds-cfg-file-based-error-log-publisher)(cn=File-Based Error Logger)",
321            "delete:ds-cfg-default-severity",
322            "ds-cfg-default-severity: severe-warning",
323            "ds-cfg-default-severity: severe-error",
324            "ds-cfg-default-severity: fatal-error",
325            "-",
326            "add:ds-cfg-default-severity",
327            "ds-cfg-default-severity: error",
328            "ds-cfg-default-severity: warning"
329            ));
330
331    register("2.8.0.10339",
332        modifyConfigEntry(INFO_UPGRADE_TASK_10339_SUMMARY.get(),
333            "&(objectclass=ds-cfg-file-based-error-log-publisher)(cn=Replication Repair Logger)",
334            "delete:ds-cfg-override-severity",
335             "-",
336             "add:ds-cfg-override-severity",
337             "ds-cfg-override-severity: SYNC=INFO,ERROR,WARNING,NOTICE"));
338
339    /** See OPENDJ-1490 and OPENDJ-1454 */
340    register("2.7.0.10703",
341        deleteConfigEntry(INFO_UPGRADE_TASK_10733_1_SUMMARY.get(),
342        "dn: ds-cfg-backend-id=replicationChanges,cn=Backends,cn=config"),
343        modifyConfigEntry(INFO_UPGRADE_TASK_10733_2_SUMMARY.get(),
344        "(objectClass=ds-cfg-dsee-compat-access-control-handler)",
345        "delete: ds-cfg-global-aci",
346        "ds-cfg-global-aci: "
347            + "(target=\"ldap:///dc=replicationchanges\")"
348            + "(targetattr=\"*\")"
349            + "(version 3.0; acl \"Replication backend access\"; "
350            + "deny (all) userdn=\"ldap:///anyone\";)"));
351
352    /** See OPENDJ-1351 */
353    register("2.7.0.10820",
354        modifyConfigEntry(INFO_UPGRADE_TASK_10820_SUMMARY.get(),
355        "(objectClass=ds-cfg-root-dn)",
356        "add: ds-cfg-default-root-privilege-name",
357        "ds-cfg-default-root-privilege-name: changelog-read"));
358
359    /** See OPENDJ-1580 */
360    register("2.7.0.10908",
361        addConfigEntry(INFO_UPGRADE_TASK_10908_SUMMARY.get(),
362            "dn: cn=PKCS5S2,cn=Password Storage Schemes,cn=config",
363            "changetype: add",
364            "objectClass: top",
365            "objectClass: ds-cfg-password-storage-scheme",
366            "objectClass: ds-cfg-pkcs5s2-password-storage-scheme",
367            "cn: PKCS5S2",
368            "ds-cfg-java-class: org.opends.server.extensions.PKCS5S2PasswordStorageScheme",
369            "ds-cfg-enabled: true"));
370
371    /** See OPENDJ-1545 */
372    register("2.8.0.11237",
373        deleteConfigEntry(INFO_UPGRADE_TASK_11237_1_SUMMARY.get(),
374            "dn: cn=Network Groups,cn=config"),
375        deleteConfigEntry(INFO_UPGRADE_TASK_11237_2_SUMMARY.get(),
376            "dn: cn=Workflows,cn=config"),
377        deleteConfigEntry(INFO_UPGRADE_TASK_11237_3_SUMMARY.get(),
378            "dn: cn=Workflow Elements,cn=config"));
379    register("2.8.0.11239",
380        deleteConfigEntry(INFO_UPGRADE_TASK_11239_SUMMARY.get(),
381            "dn: cn=Network Group,cn=Plugins,cn=config"));
382    register("2.8.0.11339",
383        deleteConfigEntry(INFO_UPGRADE_TASK_11339_SUMMARY.get(),
384            "dn: cn=Extensions,cn=config"));
385
386    /** See OPENDJ-1637 */
387    register("2.8.0.11260",
388        rebuildAllIndexes(INFO_UPGRADE_TASK_11260_SUMMARY.get()));
389
390    /** See OPENDJ-1701 */
391    register("2.8.0.11476",
392        deleteConfigEntry(INFO_UPGRADE_TASK_11476_SUMMARY.get(),
393            "dn: cn=File System,cn=Entry Caches,cn=config"));
394
395    /** See OPENDJ-1869 */
396    register("2.8.0.12226",
397        modifyConfigEntry(INFO_UPGRADE_TASK_12226_SUMMARY.get(),
398            "(objectclass=ds-cfg-root-config)",
399            "delete: ds-cfg-entry-cache-preload"));
400
401    /** See OPENDJ-2054 */
402    register("2.8.0.12451",
403        deleteFile(new File(binDirectory, "dsframework")),
404        deleteFile(new File(batDirectory, "dsframework.bat")));
405
406    /** See OPENDJ-1322 and OPENDJ-1067 */
407    register("2.7.0.9206",
408        rerunJavaPropertiesTool(INFO_UPGRADE_TASK_9206_SUMMARY.get()));
409
410    /*
411     * All upgrades will refresh the server configuration schema and generate
412     * a new upgrade folder.
413     */
414    registerLast(
415        copySchemaFile("02-config.ldif"),
416        updateConfigUpgradeFolder(),
417        postUpgradeRebuildIndexes());
418
419    // @formatter:on
420  }
421
422  /**
423   * Returns a list containing all the tasks which are required in order to
424   * upgrade from {@code fromVersion} to {@code toVersion}.
425   *
426   * @param fromVersion
427   *          The old version.
428   * @param toVersion
429   *          The new version.
430   * @return A list containing all the tasks which are required in order to
431   *         upgrade from {@code fromVersion} to {@code toVersion}.
432   */
433  private static List<UpgradeTask> getUpgradeTasks(
434      final BuildVersion fromVersion, final BuildVersion toVersion)
435  {
436    final List<UpgradeTask> tasks = new LinkedList<>();
437    for (final List<UpgradeTask> subList : TASKS.subMap(fromVersion, false,
438        toVersion, true).values())
439    {
440      tasks.addAll(subList);
441    }
442    tasks.addAll(MANDATORY_TASKS);
443    return tasks;
444  }
445
446  /**
447   * Upgrades the server from {@code fromVersion} to {@code toVersion} located
448   * in the upgrade context.
449   *
450   * @param context
451   *          The context of the upgrade.
452   * @throws ClientException
453   *           If an error occurred while performing the upgrade.
454   */
455  public static void upgrade(final UpgradeContext context)
456      throws ClientException
457  {
458    // Checks and validates the version number.
459    isVersionCanBeUpdated(context);
460
461    // Server must be offline.
462    checkIfServerIsRunning(context);
463
464    context.notify(INFO_UPGRADE_TITLE.get(), TITLE_CALLBACK);
465    context.notify(
466        INFO_UPGRADE_SUMMARY.get(context.getFromVersion(), context.getToVersion()),
467        NOTICE_CALLBACK);
468    context.notify(INFO_UPGRADE_GENERAL_SEE_FOR_DETAILS.get(UpgradeUtils
469        .getInstallationPath()
470        + File.separator + UpgradeLog.UPGRADELOGNAME), NOTICE_CALLBACK);
471
472    // Checks License.
473    checkLicence(context);
474
475    logWarnAboutPatchesFolder();
476
477    /*
478     * Get the list of required upgrade tasks.
479     */
480    final List<UpgradeTask> tasks =
481        getUpgradeTasks(context.getFromVersion(), context.getToVersion());
482    if (tasks.isEmpty())
483    {
484      changeBuildInfoVersion(context);
485      return;
486    }
487
488    /*
489     * Let tasks interact with the user in order to obtain user's selection.
490     */
491    context.notify(INFO_UPGRADE_REQUIREMENTS.get(), TITLE_CALLBACK);
492    for (final UpgradeTask task : tasks)
493    {
494      task.prepare(context);
495    }
496
497    // Starts upgrade
498    final int userResponse = context.confirmYN(INFO_UPGRADE_DISPLAY_CONFIRM_START.get(), ConfirmationCallback.YES);
499    if (userResponse == ConfirmationCallback.NO)
500    {
501      final LocalizableMessage message = INFO_UPGRADE_ABORTED_BY_USER.get();
502      context.notify(message, WARNING);
503      throw new ClientException(ReturnCode.ERROR_UNEXPECTED, message);
504    }
505
506    try
507    {
508      /*
509       * Perform the upgrade tasks.
510       */
511      context.notify(INFO_UPGRADE_PERFORMING_TASKS.get(), TITLE_CALLBACK);
512      for (final UpgradeTask task : tasks)
513      {
514        task.perform(context);
515      }
516
517      if (UpgradeTasks.countErrors == 0)
518      {
519        /*
520         * The end of a successful upgrade is marked up with the build info file
521         * update and the license, if present, requires the creation of an
522         * approval file.
523         */
524        changeBuildInfoVersion(context);
525
526        createFileLicenseApproved();
527      }
528      else
529      {
530        context.notify(ERR_UPGRADE_FAILS.get(UpgradeTasks.countErrors),
531            TITLE_CALLBACK);
532      }
533
534      /*
535       * Performs the post upgrade tasks.
536       */
537      if (hasPostUpgradeTask && UpgradeTasks.countErrors == 0)
538      {
539        context
540            .notify(INFO_UPGRADE_PERFORMING_POST_TASKS.get(), TITLE_CALLBACK);
541        performPostUpgradeTasks(context, tasks);
542        context.notify(INFO_UPGRADE_POST_TASKS_COMPLETE.get(), TITLE_CALLBACK);
543      }
544    }
545    catch (final ClientException e)
546    {
547      context.notify(e.getMessageObject(), ERROR_CALLBACK);
548      throw e;
549    }
550    catch (final Exception e)
551    {
552      final LocalizableMessage message = ERR_UPGRADE_TASKS_FAIL.get(e.getMessage());
553      context.notify(message, ERROR_CALLBACK);
554      throw new ClientException(ReturnCode.ERROR_UNEXPECTED, message);
555    }
556    finally
557    {
558      context.notify(INFO_UPGRADE_GENERAL_SEE_FOR_DETAILS.get(UpgradeUtils.getInstallationPath()
559          + File.separator + UpgradeLog.UPGRADELOGNAME), NOTICE_CALLBACK);
560      logger.info(INFO_UPGRADE_PROCESS_END);
561    }
562  }
563
564  private static void performPostUpgradeTasks(final UpgradeContext context,
565      final List<UpgradeTask> tasks) throws ClientException
566  {
567    boolean isOk = true;
568    for (final UpgradeTask task : tasks)
569    {
570      if (isOk)
571      {
572        try
573        {
574          task.postUpgrade(context);
575        }
576        catch (ClientException e)
577        {
578          context.notify(e.getMessageObject(), WARNING);
579          isOk = false;
580        }
581      }
582      else
583      {
584        task.postponePostUpgrade(context);
585      }
586    }
587  }
588
589  private static void register(final String versionString,
590      final UpgradeTask... tasks)
591  {
592    final BuildVersion version = BuildVersion.valueOf(versionString);
593    List<UpgradeTask> taskList = TASKS.get(version);
594    if (taskList == null)
595    {
596      taskList = new LinkedList<>();
597      TASKS.put(version, taskList);
598    }
599    taskList.addAll(Arrays.asList(tasks));
600  }
601
602  private static void registerLast(final UpgradeTask... tasks)
603  {
604    MANDATORY_TASKS.addAll(Arrays.asList(tasks));
605  }
606
607  /**
608   * The server must be offline during the upgrade.
609   *
610   * @throws ClientException
611   *           An exception is thrown if the server is currently running.
612   */
613  private static void checkIfServerIsRunning(final UpgradeContext context) throws ClientException
614  {
615    final String lockFile = LockFileManager.getServerLockFileName();
616
617    final StringBuilder failureReason = new StringBuilder();
618    try
619    {
620      // Assume that if we cannot acquire the lock file the server is
621      // running.
622      if (!LockFileManager.acquireExclusiveLock(lockFile, failureReason))
623      {
624        final LocalizableMessage message = ERR_UPGRADE_REQUIRES_SERVER_OFFLINE.get();
625        context.notify(message, NOTICE_CALLBACK);
626        throw new ClientException(ReturnCode.ERROR_UNEXPECTED, message);
627      }
628    }
629    finally
630    {
631      LockFileManager.releaseLock(lockFile, failureReason);
632    }
633  }
634
635  /**
636   * Checks if the version can be updated.
637   *
638   * @param context
639   *          The current context which running the upgrade.
640   * @throws ClientException
641   *           If an exception occurs - stops the process.
642   */
643  private static void isVersionCanBeUpdated(final UpgradeContext context)
644      throws ClientException
645  {
646    if (context.getFromVersion().equals(context.getToVersion()))
647    {
648      /*
649       * If the server is already up to date then treat it as a successful
650       * upgrade so that upgrade is idempotent.
651       */
652      final LocalizableMessage message =
653          ERR_UPGRADE_VERSION_UP_TO_DATE.get(context.getToVersion());
654      context.notify(message, NOTICE_CALLBACK);
655      throw new ClientException(ReturnCode.SUCCESS, message);
656    }
657
658    // The upgrade only supports version >= 2.4.5.
659    if (context.getFromVersion().compareTo(UPGRADESUPPORTSVERSIONFROM) < 0)
660    {
661      final LocalizableMessage message = INFO_UPGRADE_VERSION_IS_NOT_SUPPORTED.get(
662              UPGRADESUPPORTSVERSIONFROM, UPGRADESUPPORTSVERSIONFROM);
663      context.notify(message, NOTICE_CALLBACK);
664      throw new ClientException(ReturnCode.ERROR_UNEXPECTED, message);
665    }
666  }
667
668  /**
669   * Writes the up to date's version number within the build info file.
670   *
671   * @param context
672   *          The current context which running the upgrade.
673   * @throws ClientException
674   *           If an exception occurs when displaying the message.
675   * @throws IOException
676   *           If an exception occurs when trying to write the file.
677   */
678  private static void changeBuildInfoVersion(final UpgradeContext context)
679      throws ClientException
680  {
681    FileWriter buildInfo = null;
682    try
683    {
684      buildInfo =
685          new FileWriter(new File(UpgradeUtils.configDirectory,
686              Installation.BUILDINFO_RELATIVE_PATH), false);
687
688      // Write the new version
689      buildInfo.write(context.getToVersion().toString());
690
691      context.notify(INFO_UPGRADE_SUCCESSFUL.get(
692          context.getFromVersion(), context.getToVersion()), TITLE_CALLBACK);
693    }
694    catch (IOException e)
695    {
696      final LocalizableMessage message = LocalizableMessage.raw(e.getMessage());
697      context.notify(message, ERROR_CALLBACK);
698      throw new ClientException(ReturnCode.ERROR_UNEXPECTED, message);
699    }
700    finally
701    {
702      StaticUtils.close(buildInfo);
703    }
704  }
705
706  private static void checkLicence(final UpgradeContext context)
707      throws ClientException
708  {
709    // Check license
710    if (LicenseFile.exists() && !LicenseFile.isAlreadyApproved())
711    {
712      context.notify(LocalizableMessage.raw(LINE_SEPARATOR + LicenseFile.getText()));
713      context.notify(INFO_LICENSE_DETAILS_CLI_LABEL.get());
714      if (!context.isAcceptLicenseMode())
715      {
716        final int answer;
717
718        // The force cannot answer yes to the license's question,
719        // which is not a task even if it requires a user interaction OR
720        // -an accept license mode to continue the process.
721        if (context.isForceUpgradeMode())
722        {
723          answer = ConfirmationCallback.NO;
724          context.notify(LocalizableMessage.raw(INFO_LICENSE_ACCEPT.get() + " "
725              + INFO_PROMPT_NO_COMPLETE_ANSWER.get()));
726        }
727        else
728        {
729          answer = context.confirmYN(INFO_LICENSE_ACCEPT.get(), ConfirmationCallback.NO);
730        }
731
732        if (answer == ConfirmationCallback.NO)
733        {
734          System.exit(EXIT_CODE_SUCCESS);
735        }
736        else if (answer == ConfirmationCallback.YES)
737        {
738          LicenseFile.setApproval(true);
739        }
740      }
741      else
742      {
743        // We automatically accept the license with this option.
744        context.notify(LocalizableMessage.raw(INFO_LICENSE_ACCEPT.get() + " "
745            + INFO_PROMPT_YES_COMPLETE_ANSWER.get()));
746        LicenseFile.setApproval(true);
747      }
748    }
749  }
750
751  /**
752   * The classes folder is renamed by the script launcher to avoid
753   * incompatibility between patches and upgrade process. If a folder
754   * "classes.disabled" is found, this function just displays a warning in the
755   * log file, meaning the "classes" folder has been renamed. See upgrade.sh /
756   * upgrade.bat scripts which hold the renaming process. (OPENDJ-1098)
757   */
758  private static void logWarnAboutPatchesFolder()
759  {
760    try
761    {
762      final File backup = new File(UpgradeUtils.getInstancePath(), "classes.disabled");
763      if (backup.exists()) {
764        final File[] files = backup.listFiles();
765        if (files != null && files.length > 0)
766        {
767          logger.warn(INFO_UPGRADE_CLASSES_FOLDER_RENAMED, backup.getAbsoluteFile());
768        }
769      }
770    }
771    catch (SecurityException se)
772    {
773      logger.debug(LocalizableMessage.raw(se.getMessage()));
774    }
775  }
776
777  /**
778   * Returns {@code true} if the current upgrade contains post upgrade tasks.
779   *
780   * @return {@code true} if the current upgrade contains post upgrade tasks.
781   */
782  static boolean hasPostUpgradeTask()
783  {
784    return hasPostUpgradeTask;
785  }
786
787  /**
788   * Sets {@code true} if the current upgrade contains post upgrade tasks.
789   *
790   * @param hasPostUpgradeTask
791   *          {@code true} if the current upgrade contains post upgrade tasks.
792   */
793  static void setHasPostUpgradeTask(boolean hasPostUpgradeTask)
794  {
795    Upgrade.hasPostUpgradeTask = hasPostUpgradeTask;
796  }
797
798  /** Prevent instantiation. */
799  private Upgrade()
800  {
801    // Nothing to do.
802  }
803}