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