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