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 static javax.security.auth.callback.ConfirmationCallback.NO;
019import static javax.security.auth.callback.ConfirmationCallback.YES;
020import static javax.security.auth.callback.TextOutputCallback.*;
021
022import static org.opends.messages.ToolMessages.*;
023import static org.opends.server.tools.upgrade.FileManager.copy;
024import static org.opends.server.tools.upgrade.Installation.CURRENT_CONFIG_FILE_NAME;
025import static org.opends.server.tools.upgrade.UpgradeUtils.*;
026import static org.opends.server.util.StaticUtils.isClassAvailable;
027
028import java.io.File;
029import java.io.IOException;
030import java.util.Arrays;
031import java.util.Collections;
032import java.util.HashSet;
033import java.util.LinkedList;
034import java.util.List;
035import java.util.Map;
036import java.util.Set;
037import java.util.TreeMap;
038import java.util.TreeSet;
039
040import javax.security.auth.callback.TextOutputCallback;
041
042import org.forgerock.i18n.LocalizableMessage;
043import org.forgerock.i18n.slf4j.LocalizedLogger;
044import org.forgerock.opendj.ldap.DN;
045import org.forgerock.opendj.ldap.Entry;
046import org.forgerock.opendj.ldap.Filter;
047import org.forgerock.opendj.ldap.SearchScope;
048import org.forgerock.opendj.ldap.requests.Requests;
049import org.forgerock.opendj.ldap.requests.SearchRequest;
050import org.forgerock.opendj.ldif.EntryReader;
051import org.forgerock.util.Utils;
052import org.opends.server.backends.pluggable.spi.TreeName;
053import org.opends.server.tools.JavaPropertiesTool;
054import org.opends.server.tools.RebuildIndex;
055import org.opends.server.util.BuildVersion;
056import org.opends.server.util.ChangeOperationType;
057import org.opends.server.util.StaticUtils;
058
059import com.forgerock.opendj.cli.ClientException;
060import com.forgerock.opendj.cli.ReturnCode;
061import com.sleepycat.je.DatabaseException;
062import com.sleepycat.je.Environment;
063import com.sleepycat.je.EnvironmentConfig;
064import com.sleepycat.je.Transaction;
065import com.sleepycat.je.TransactionConfig;
066
067/** Factory methods for create new upgrade tasks. */
068public final class UpgradeTasks
069{
070  /** Logger for the upgrade. */
071  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
072
073  /** An errors counter in case of ignore errors mode. */
074  static int countErrors;
075
076  /** Contains all the indexes to rebuild. */
077  static Set<String> indexesToRebuild = new HashSet<>();
078
079  /** A flag to avoid rebuild single indexes if 'rebuild all' is selected. */
080  static boolean isRebuildAllIndexesIsPresent;
081
082  /** A flag for marking 'rebuild all' task accepted by user. */
083  static boolean isRebuildAllIndexesTaskAccepted;
084
085  private static final List<String> SUPPORTED_LOCALES_FOR_3_0_0 = Arrays.asList(
086      "ca_ES", "de", "es", "fr", "ja", "ko", "pl", "zh_CN", "zh_TW");
087
088  /**
089   * Returns a new upgrade task which adds a config entry to the underlying
090   * config file.
091   *
092   * @param summary
093   *          The summary of this upgrade task.
094   * @param ldif
095   *          The LDIF record which will be applied to matching entries.
096   * @return A new upgrade task which applies an LDIF record to all
097   *         configuration entries matching the provided filter.
098   */
099  public static UpgradeTask addConfigEntry(final LocalizableMessage summary,
100      final String... ldif)
101  {
102    return updateConfigEntry(summary, null, ChangeOperationType.ADD, ldif);
103  }
104
105  /**
106   * This task copies the file placed in parameter within the config / schema
107   * folder. If the file already exists, it's overwritten.
108   *
109   * @param fileName
110   *          The name of the file which need to be copied.
111   * @return A task which copy the the file placed in parameter within the
112   *         config / schema folder. If the file already exists, it's
113   *         overwritten.
114   */
115  public static UpgradeTask copySchemaFile(final String fileName)
116  {
117    return new AbstractUpgradeTask()
118    {
119      @Override
120      public void perform(final UpgradeContext context) throws ClientException
121      {
122        final LocalizableMessage msg = INFO_UPGRADE_TASK_REPLACE_SCHEMA_FILE.get(fileName);
123        logger.debug(msg);
124
125        final ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, msg, 0);
126
127        final File schemaFileTemplate =
128            new File(templateConfigSchemaDirectory, fileName);
129
130        try
131        {
132          context.notifyProgress(pnc.setProgress(20));
133          if (!schemaFileTemplate.exists() || schemaFileTemplate.length() == 0)
134          {
135            throw new IOException(ERR_UPGRADE_CORRUPTED_TEMPLATE
136                .get(schemaFileTemplate.getPath()).toString());
137          }
138          copy(schemaFileTemplate, configSchemaDirectory, true);
139          context.notifyProgress(pnc.setProgress(100));
140        }
141        catch (final IOException e)
142        {
143          manageTaskException(context, ERR_UPGRADE_COPYSCHEMA_FAILS.get(
144              schemaFileTemplate.getName(), e.getMessage()), pnc);
145        }
146      }
147
148      @Override
149      public String toString()
150      {
151        return INFO_UPGRADE_TASK_REPLACE_SCHEMA_FILE.get(fileName).toString();
152      }
153    };
154  }
155
156  /**
157   * This task copies the file placed in parameter within the config folder. If
158   * the file already exists, it's overwritten.
159   *
160   * @param fileName
161   *          The name of the file which need to be copied.
162   * @return A task which copy the the file placed in parameter within the
163   *         config folder. If the file already exists, it's overwritten.
164   */
165  public static UpgradeTask addConfigFile(final String fileName)
166  {
167    return new AbstractUpgradeTask()
168    {
169      @Override
170      public void perform(final UpgradeContext context) throws ClientException
171      {
172        final LocalizableMessage msg = INFO_UPGRADE_TASK_ADD_CONFIG_FILE.get(fileName);
173        logger.debug(msg);
174
175        final ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, msg, 0);
176
177        final File configFile = new File(templateConfigDirectory, fileName);
178
179        try
180        {
181          context.notifyProgress(pnc.setProgress(20));
182
183          copy(configFile, configDirectory, true);
184          context.notifyProgress(pnc.setProgress(100));
185        }
186        catch (final IOException e)
187        {
188          manageTaskException(context, ERR_UPGRADE_ADD_CONFIG_FILE_FAILS.get(
189              configFile.getName(), e.getMessage()), pnc);
190        }
191      }
192
193      @Override
194      public String toString()
195      {
196        return INFO_UPGRADE_TASK_ADD_CONFIG_FILE.get(fileName).toString();
197      }
198    };
199  }
200
201  /**
202   * Returns a new upgrade task which deletes a config entry from the underlying config file.
203   *
204   * @param summary
205   *          The summary of this upgrade task.
206   * @param dnsInLDIF
207   *          The dns to delete in the form of LDIF.
208   * @return A new upgrade task which applies an LDIF record to all configuration entries matching
209   *         the provided filter.
210   */
211  public static UpgradeTask deleteConfigEntry(final LocalizableMessage summary, final String... dnsInLDIF)
212  {
213    return updateConfigEntry(summary, null, ChangeOperationType.DELETE, dnsInLDIF);
214  }
215
216  /**
217   * Returns a new upgrade task which applies an LDIF record to all
218   * configuration entries matching the provided filter.
219   *
220   * @param summary
221   *          The summary of this upgrade task.
222   * @param filter
223   *          The LDAP filter which configuration entries must match.
224   * @param ldif
225   *          The LDIF record which will be applied to matching entries.
226   * @return A new upgrade task which applies an LDIF record to all
227   *         configuration entries matching the provided filter.
228   */
229  public static UpgradeTask modifyConfigEntry(final LocalizableMessage summary,
230      final String filter, final String... ldif)
231  {
232    return updateConfigEntry(summary, filter, ChangeOperationType.MODIFY, ldif);
233  }
234
235  /**
236   * This task adds or updates an attribute type (must exist in the original file)
237   * to the file specified in {@code fileName}. The destination must be a file
238   * contained in the config/schema folder. The attribute type is updated if an
239   * attribute with the same OID exists.
240   *
241   * e.g : This example adds a new attribute type named 'etag' in the 00-core.ldif.
242   * The 'etag' attribute already exists in the 00-core.ldif template schema file.
243   *
244   * <pre>
245   * register(&quot;2.5.0&quot;,
246   *   newAttributeTypes(LocalizableMessage.raw(&quot;New attribute etag&quot;),
247   *   false, &quot;00-core.ldif&quot;,
248   *   &quot;1.3.6.1.4.1.36733.2.1.1.59&quot;));
249   * </pre>
250   *
251   * @param summary
252   *          The summary of the task.
253   * @param fileName
254   *          The file where to add the new definitions. This file must be
255   *          contained in the configuration/schema folder.
256   * @param attributeOids
257   *          The OIDs of the attributes to add or update.
258   * @return An upgrade task which adds or updates attribute types, defined
259   *         previously in the configuration template files, reads the
260   *         definition and adds it onto the file specified in {@code fileName}
261   */
262  public static UpgradeTask newAttributeTypes(final LocalizableMessage summary,
263      final String fileName, final String... attributeOids)
264  {
265    return new AbstractUpgradeTask()
266    {
267      @Override
268      public void perform(final UpgradeContext context) throws ClientException
269      {
270        logger.debug(summary);
271
272        final ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, summary, 20);
273        context.notifyProgress(pnc);
274
275        final File schemaFileTemplate =
276            new File(templateConfigSchemaDirectory, fileName);
277
278        final File pathDestination = new File(configSchemaDirectory, fileName);
279        try
280        {
281          final int changeCount =
282              updateSchemaFile(schemaFileTemplate, pathDestination,
283                  attributeOids, null);
284
285          displayChangeCount(pathDestination.getPath(), changeCount);
286
287          context.notifyProgress(pnc.setProgress(100));
288        }
289        catch (final IOException | IllegalStateException e)
290        {
291          manageTaskException(context, ERR_UPGRADE_ADDATTRIBUTE_FAILS.get(
292              schemaFileTemplate.getName(), e.getMessage()), pnc);
293        }
294      }
295
296      @Override
297      public String toString()
298      {
299        return String.valueOf(summary);
300      }
301    };
302  }
303
304  /**
305   * This task adds or updates an object class (must exist in the original file)
306   * to the file specified in {@code fileName}. The destination must be a file
307   * contained in the config/schema folder. The object class will be updated if
308   * a definition with the same OID exists, and added otherwise.
309   *
310   * @param summary
311   *          The summary of the task.
312   * @param fileName
313   *          The file where to add the new definitions. This file must be
314   *          contained in the configuration/schema folder.
315   * @param objectClassesOids
316   *          The OIDs of the object classes to add or update.
317   * @return An upgrade task which adds or updates object classes, defined
318   *         previously in the configuration template files, reads the
319   *         definition and adds it onto the file specified in {@code fileName}
320   */
321  public static UpgradeTask newObjectClasses(final LocalizableMessage summary,
322      final String fileName, final String... objectClassesOids)
323  {
324    return new AbstractUpgradeTask()
325    {
326      @Override
327      public void perform(final UpgradeContext context) throws ClientException
328      {
329        logger.debug(summary);
330
331        final ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, summary, 20);
332        context.notifyProgress(pnc);
333
334        final File schemaFileTemplate =
335            new File(templateConfigSchemaDirectory, fileName);
336
337        final File pathDestination = new File(configSchemaDirectory, fileName);
338
339        context.notifyProgress(pnc.setProgress(20));
340
341        try
342        {
343          final int changeCount =
344              updateSchemaFile(schemaFileTemplate, pathDestination,
345                  null, objectClassesOids);
346
347          displayChangeCount(pathDestination.getPath(), changeCount);
348
349          context.notifyProgress(pnc.setProgress(100));
350        }
351        catch (final IOException e)
352        {
353          manageTaskException(context, ERR_UPGRADE_ADDOBJECTCLASS_FAILS.get(
354              schemaFileTemplate.getName(), e.getMessage()), pnc);
355        }
356        catch (final IllegalStateException e)
357        {
358          manageTaskException(context, ERR_UPGRADE_ADDATTRIBUTE_FAILS.get(
359              schemaFileTemplate.getName(), e.getMessage()), pnc);
360        }
361      }
362
363      @Override
364      public String toString()
365      {
366        return String.valueOf(summary);
367      }
368    };
369  }
370
371  /**
372   * Re-run the dsjavaproperties tool to rewrite the set-java-home script/batch file.
373   *
374   * @param summary
375   *          The summary of the task.
376   * @return An upgrade task which runs dsjavaproperties.
377   */
378  public static UpgradeTask rerunJavaPropertiesTool(final LocalizableMessage summary)
379  {
380    return new AbstractUpgradeTask()
381    {
382      @Override
383      public void perform(UpgradeContext context) throws ClientException
384      {
385        logger.debug(summary);
386
387        final ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, summary, 50);
388        context.notifyProgress(pnc);
389
390        int returnValue = JavaPropertiesTool.mainCLI("--quiet");
391        context.notifyProgress(pnc.setProgress(100));
392
393        if (JavaPropertiesTool.ErrorReturnCode.SUCCESSFUL.getReturnCode() != returnValue &&
394                JavaPropertiesTool.ErrorReturnCode.SUCCESSFUL_NOP.getReturnCode() != returnValue) {
395          throw new ClientException(ReturnCode.ERROR_UNEXPECTED, ERR_UPGRADE_DSJAVAPROPERTIES_FAILED.get());
396        }
397      }
398
399      @Override
400      public String toString()
401      {
402        return String.valueOf(summary);
403      }
404    };
405  }
406
407  /**
408   * Creates a group of tasks which will only be invoked if the current version
409   * is more recent than the provided version. This may be useful in cases where
410   * a regression was introduced in version X and resolved in a later version Y.
411   * In this case, the provided upgrade tasks will only be invoked if the
412   * current version is between X (inclusive) and Y (exclusive).
413   *
414   * @param versionString
415   *          The lower bound version. The upgrade tasks will not be applied if
416   *          the current version is older than this version.
417   * @param tasks
418   *          The group of tasks to invoke if the current version is equal to or
419   *          more recent than {@code versionString}.
420   * @return An upgrade task which will only be invoked if the current version
421   *         is more recent than the provided version.
422   */
423  public static UpgradeTask regressionInVersion(final String versionString, final UpgradeTask... tasks)
424  {
425    final BuildVersion version = BuildVersion.valueOf(versionString);
426    return conditionalUpgradeTasks(new UpgradeCondition()
427    {
428      @Override
429      public boolean shouldPerformUpgradeTasks(final UpgradeContext context) throws ClientException
430      {
431        return context.getFromVersion().compareTo(version) >= 0;
432      }
433
434      @Override
435      public String toString()
436      {
437        return "Regression in version \"" + versionString + "\"";
438      }
439    }, tasks);
440  }
441
442  /**
443   * Creates a group of tasks which will only be invoked if the user confirms agreement. This may be
444   * useful in cases where a feature is deprecated and the upgrade is capable of migrating the
445   * configuration to the new replacement feature.
446   *
447   * @param message
448   *          The confirmation message.
449   * @param tasks
450   *          The group of tasks to invoke if the user agrees.
451   * @return An upgrade task which will only be invoked if the user confirms agreement.
452   */
453  static UpgradeTask requireConfirmation(
454          final LocalizableMessage message, final int defaultResponse, final UpgradeTask... tasks)
455  {
456    return conditionalUpgradeTasks(new UpgradeCondition()
457    {
458      @Override
459      public boolean shouldPerformUpgradeTasks(final UpgradeContext context) throws ClientException
460      {
461        return context.confirmYN(INFO_UPGRADE_TASK_NEEDS_USER_CONFIRM.get(message), defaultResponse) == YES;
462      }
463
464      @Override
465      public String toString()
466      {
467        return INFO_UPGRADE_TASK_NEEDS_USER_CONFIRM.get(message).toString();
468      }
469    }, tasks);
470  }
471
472  /** Determines whether conditional tasks should be performed. */
473  interface UpgradeCondition
474  {
475    boolean shouldPerformUpgradeTasks(UpgradeContext context) throws ClientException;
476  }
477
478  static UpgradeTask conditionalUpgradeTasks(final UpgradeCondition condition, final UpgradeTask... tasks)
479  {
480    return new AbstractUpgradeTask()
481    {
482      private boolean shouldPerformUpgradeTasks = true;
483
484      @Override
485      public void prepare(final UpgradeContext context) throws ClientException
486      {
487        shouldPerformUpgradeTasks = condition.shouldPerformUpgradeTasks(context);
488        if (shouldPerformUpgradeTasks)
489        {
490          for (UpgradeTask task : tasks)
491          {
492            task.prepare(context);
493          }
494        }
495      }
496
497      @Override
498      public void perform(final UpgradeContext context) throws ClientException
499      {
500        if (shouldPerformUpgradeTasks)
501        {
502          for (UpgradeTask task : tasks)
503          {
504            task.perform(context);
505          }
506        }
507      }
508
509      @Override
510      public void postUpgrade(UpgradeContext context) throws ClientException
511      {
512        if (shouldPerformUpgradeTasks)
513        {
514          boolean isOk = true;
515          for (final UpgradeTask task : tasks)
516          {
517            if (isOk)
518            {
519              try
520              {
521                task.postUpgrade(context);
522              }
523              catch (ClientException e)
524              {
525                logger.error(LocalizableMessage.raw(e.getMessage()));
526                isOk = false;
527              }
528            }
529            else
530            {
531              task.postponePostUpgrade(context);
532            }
533          }
534        }
535      }
536
537      @Override
538      public String toString()
539      {
540        final StringBuilder sb = new StringBuilder();
541        sb.append(condition).append(" = ").append(shouldPerformUpgradeTasks).append('\n');
542        sb.append('[');
543        Utils.joinAsString(sb, "\n", (Object[]) tasks);
544        sb.append(']');
545        return sb.toString();
546      }
547    };
548  }
549
550  /**
551   * Creates a rebuild all indexes task.
552   *
553   * @param summary
554   *          The summary of this upgrade task.
555   * @return An Upgrade task which rebuild all the indexes.
556   */
557  public static UpgradeTask rebuildAllIndexes(final LocalizableMessage summary)
558  {
559    return new AbstractUpgradeTask()
560    {
561      private boolean isATaskToPerform;
562
563      @Override
564      public void prepare(UpgradeContext context) throws ClientException
565      {
566        Upgrade.needToRunPostUpgradePhase();
567        // Requires answer from the user.
568        isATaskToPerform = context.confirmYN(summary, NO) == YES;
569        isRebuildAllIndexesIsPresent = true;
570        isRebuildAllIndexesTaskAccepted = isATaskToPerform;
571      }
572
573      @Override
574      public void postUpgrade(final UpgradeContext context) throws ClientException
575      {
576        if (!isATaskToPerform)
577        {
578          postponePostUpgrade(context);
579        }
580      }
581
582      @Override
583      public void postponePostUpgrade(UpgradeContext context) throws ClientException
584      {
585        context.notify(INFO_UPGRADE_ALL_REBUILD_INDEX_DECLINED.get(), TextOutputCallback.WARNING);
586      }
587
588      @Override
589      public String toString()
590      {
591        return String.valueOf(summary);
592      }
593    };
594  }
595
596  /**
597   * Creates a rebuild index task for a given single index. As this task is
598   * possibly lengthy, it's considered as a post upgrade task. This task is not
599   * mandatory; e.g not require user interaction, but could be required to get a
600   * fully functional server. <br />
601   * The post upgrade task just register the task. The rebuild indexes tasks are
602   * completed at the end of the upgrade process.
603   *
604   * @param summary
605   *          A message describing why the index needs to be rebuilt and asking
606   *          them whether or not they wish to perform this task after the
607   *          upgrade.
608   * @param indexNames
609   *          The indexes to rebuild.
610   * @return The rebuild index task.
611   */
612  public static UpgradeTask rebuildIndexesNamed(final LocalizableMessage summary, final String... indexNames)
613  {
614    return new AbstractUpgradeTask()
615    {
616      private boolean isATaskToPerform;
617
618      @Override
619      public void prepare(UpgradeContext context) throws ClientException
620      {
621        Upgrade.needToRunPostUpgradePhase();
622        // Requires answer from the user.
623        isATaskToPerform = context.confirmYN(summary, NO) == YES;
624      }
625
626      @Override
627      public void postUpgrade(final UpgradeContext context) throws ClientException
628      {
629        if (isATaskToPerform)
630        {
631          Collections.addAll(indexesToRebuild, indexNames);
632        }
633        else
634        {
635          postponePostUpgrade(context);
636        }
637      }
638
639      @Override
640      public void postponePostUpgrade(UpgradeContext context) throws ClientException
641      {
642        if (!isRebuildAllIndexesIsPresent)
643        {
644          context.notify(INFO_UPGRADE_REBUILD_INDEXES_DECLINED.get(indexNames), TextOutputCallback.WARNING);
645        }
646      }
647
648      @Override
649      public String toString()
650      {
651        return String.valueOf(summary);
652      }
653    };
654  }
655
656  /**
657   * This task is processed at the end of the upgrade, rebuilding indexes. If a
658   * rebuild all indexes has been registered before, it takes the flag
659   * relatively to single rebuild index.
660   *
661   * @return The post upgrade rebuild indexes task.
662   */
663  public static UpgradeTask postUpgradeRebuildIndexes()
664  {
665    return new AbstractUpgradeTask()
666    {
667      @Override
668      public void postUpgrade(final UpgradeContext context) throws ClientException
669      {
670        LocalizableMessage message = null;
671        final List<String> args = new LinkedList<>();
672
673        if (isRebuildAllIndexesIsPresent && isRebuildAllIndexesTaskAccepted)
674        {
675          args.add("--rebuildAll");
676          message = INFO_UPGRADE_REBUILD_ALL.get();
677        }
678        else if (!indexesToRebuild.isEmpty()
679            && !isRebuildAllIndexesTaskAccepted)
680        {
681          message = INFO_UPGRADE_REBUILD_INDEX_STARTS.get(indexesToRebuild);
682
683          // Adding all requested indexes.
684          for (final String indexToRebuild : indexesToRebuild)
685          {
686            args.add("-i");
687            args.add(indexToRebuild);
688          }
689        }
690        else
691        {
692          return;
693        }
694        // Startup message.
695        ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, message, 25);
696        logger.debug(message);
697        context.notifyProgress(pnc);
698
699        // Sets the arguments like the rebuild index command line.
700        args.addAll(Arrays.asList("-f", getConfigLdifFile().getAbsolutePath()));
701
702        /*
703         * Index(es) could be contained in several backends or none, If none,
704         * the post upgrade tasks succeed and a message is printed in the
705         * upgrade log file.
706         */
707        final List<String> backends = UpgradeUtils.getIndexedBackendsFromConfig();
708        if (backends.isEmpty())
709        {
710          logger.debug(INFO_UPGRADE_REBUILD_INDEX_NO_BACKEND_FOUND);
711          logger.debug(INFO_UPGRADE_REBUILD_INDEXES_DECLINED, indexesToRebuild);
712          context.notifyProgress(pnc.setProgress(100));
713          return;
714        }
715
716        for (final String be : backends)
717        {
718          args.add("-b");
719          args.add(be);
720        }
721
722        // Displays info about command line args for log only.
723        logger.debug(INFO_UPGRADE_REBUILD_INDEX_ARGUMENTS, args);
724
725        /*
726         * The rebuild-index process just display a status ok / fails. The
727         * logger stream contains all the log linked to this process. The
728         * complete process is not displayed in the upgrade console.
729         */
730        final String[] commandLineArgs = args.toArray(new String[args.size()]);
731        final int result = new RebuildIndex().rebuildIndexesWithinMultipleBackends(
732            true, UpgradeLog.getPrintStream(), commandLineArgs);
733
734        if (result == 0)
735        {
736          logger.debug(INFO_UPGRADE_REBUILD_INDEX_ENDS);
737          context.notifyProgress(pnc.setProgress(100));
738        }
739        else
740        {
741          final LocalizableMessage msg = ERR_UPGRADE_PERFORMING_POST_TASKS_FAIL.get();
742          context.notifyProgress(pnc.setProgress(-100));
743          throw new ClientException(ReturnCode.ERROR_UNEXPECTED, msg);
744        }
745      }
746
747      @Override
748      public String toString()
749      {
750        return "Post upgrade rebuild indexes task";
751      }
752    };
753  }
754
755  /**
756   * Creates a file object representing config/upgrade/schema.ldif.current which
757   * the server creates the first time it starts if there are schema
758   * customizations.
759   *
760   * @return An upgrade task which upgrade the config/upgrade folder, creating a
761   *         new schema.ldif.rev which is needed after schema customization for
762   *         starting correctly the server.
763   */
764  public static UpgradeTask updateConfigUpgradeFolder()
765  {
766    return new AbstractUpgradeTask()
767    {
768      @Override
769      public void perform(final UpgradeContext context) throws ClientException
770      {
771        final LocalizableMessage msg = INFO_UPGRADE_TASK_REFRESH_UPGRADE_DIRECTORY.get();
772        logger.debug(msg);
773
774        final ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, msg, 20);
775        context.notifyProgress(pnc);
776
777        try
778        {
779          String toRevision = context.getToVersion().getRevision();
780          updateConfigUpgradeSchemaFile(configSchemaDirectory, toRevision);
781
782          context.notifyProgress(pnc.setProgress(100));
783        }
784        catch (final Exception ex)
785        {
786          manageTaskException(context, ERR_UPGRADE_CONFIG_ERROR_UPGRADE_FOLDER.get(ex.getMessage()), pnc);
787        }
788      }
789
790      @Override
791      public String toString()
792      {
793        return INFO_UPGRADE_TASK_REFRESH_UPGRADE_DIRECTORY.get().toString();
794      }
795    };
796  }
797
798  /**
799   * Renames the SNMP security config file if it exists. Since 2.5.0.7466 this
800   * file has been renamed.
801   *
802   * @param summary
803   *          The summary of this upgrade task.
804   * @return An upgrade task which renames the old SNMP security config file if
805   *         it exists.
806   */
807  public static UpgradeTask renameSnmpSecurityConfig(final LocalizableMessage summary)
808  {
809    return new AbstractUpgradeTask()
810    {
811      @Override
812      public void perform(final UpgradeContext context) throws ClientException
813      {
814        /*
815         * Snmp config file contains old name in old version(like 2.4.5), in
816         * order to make sure the process will still work after upgrade, we need
817         * to rename it - only if it exists.
818         */
819        final File snmpDir = UpgradeUtils.configSnmpSecurityDirectory;
820        if (snmpDir.exists())
821        {
822          ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, summary, 0);
823          try
824          {
825            final File oldSnmpConfig = new File(snmpDir, "opends-snmp.security");
826            if (oldSnmpConfig.exists())
827            {
828              context.notifyProgress(pnc.setProgress(20));
829              logger.debug(summary);
830
831              final File snmpConfig = new File(snmpDir, "opendj-snmp.security");
832              FileManager.rename(oldSnmpConfig, snmpConfig);
833
834              context.notifyProgress(pnc.setProgress(100));
835            }
836          }
837          catch (final Exception ex)
838          {
839            LocalizableMessage msg = ERR_UPGRADE_RENAME_SNMP_SECURITY_CONFIG_FILE.get(ex.getMessage());
840            manageTaskException(context, msg, pnc);
841          }
842        }
843      }
844
845      @Override
846      public String toString()
847      {
848        return String.valueOf(summary);
849      }
850    };
851  }
852
853  /**
854   * Removes the specified file from the file-system.
855   *
856   * @param file
857   *          The file to be removed.
858   * @return An upgrade task which removes the specified file from the file-system.
859   */
860  public static UpgradeTask deleteFile(final File file)
861  {
862    return new AbstractUpgradeTask()
863    {
864      @Override
865      public void perform(UpgradeContext context) throws ClientException
866      {
867        LocalizableMessage msg = INFO_UPGRADE_TASK_DELETE_FILE.get(file);
868        ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, msg, 0);
869        context.notifyProgress(pnc);
870        try
871        {
872          FileManager.deleteRecursively(file);
873          context.notifyProgress(pnc.setProgress(100));
874        }
875        catch (Exception e)
876        {
877          manageTaskException(context, LocalizableMessage.raw(e.getMessage()), pnc);
878        }
879      }
880
881      @Override
882      public String toString()
883      {
884        return INFO_UPGRADE_TASK_DELETE_FILE.get(file).toString();
885      }
886    };
887  }
888
889  /**
890   * Creates an upgrade task which is responsible for preparing local-db backend JE databases for a full rebuild once
891   * they have been converted to pluggable JE backends.
892   *
893   * @return An upgrade task which is responsible for preparing local-db backend JE databases.
894   */
895  public static UpgradeTask migrateLocalDBBackendsToJEBackends() {
896    return new AbstractUpgradeTask() {
897      /** Properties of JE backends to be migrated. */
898      class Backend {
899        final String id;
900        final boolean isEnabled;
901        final Set<DN> baseDNs;
902        final File envDir;
903        final Map<String, String> renamedDbs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
904
905        private Backend(Entry config) {
906          id = config.parseAttribute("ds-cfg-backend-id").asString();
907          isEnabled = config.parseAttribute("ds-cfg-enabled").asBoolean(false);
908          baseDNs = config.parseAttribute("ds-cfg-base-dn").asSetOfDN();
909          String dbDirectory = config.parseAttribute("ds-cfg-db-directory").asString();
910          File backendParentDirectory = new File(dbDirectory);
911          if (!backendParentDirectory.isAbsolute()) {
912            backendParentDirectory = new File(getInstancePath(), dbDirectory);
913          }
914          envDir = new File(backendParentDirectory, id);
915          for (String db : Arrays.asList("compressed_attributes", "compressed_object_classes")) {
916            renamedDbs.put(db, new TreeName("compressed_schema", db).toString());
917          }
918          for (DN baseDN : baseDNs) {
919            renamedDbs.put(oldName(baseDN), newName(baseDN));
920          }
921        }
922      }
923
924      private final List<Backend> backends = new LinkedList<>();
925
926      /**
927       * Finds all the existing JE backends and determines if they can be migrated or not. It will not be possible to
928       * migrate a JE backend if the id2entry database name cannot easily be determined, which may happen because
929       * matching rules have changed significantly in 3.0.0.
930       */
931      @Override
932      public void prepare(final UpgradeContext context) throws ClientException {
933        // Requires answer from the user.
934        if (context.confirmYN(INFO_UPGRADE_TASK_MIGRATE_JE_DESCRIPTION.get(), NO) != YES) {
935          throw new ClientException(ReturnCode.ERROR_USER_CANCELLED,
936                                    INFO_UPGRADE_TASK_MIGRATE_JE_CANCELLED.get());
937        }
938
939        final SearchRequest sr = Requests.newSearchRequest("", SearchScope.WHOLE_SUBTREE,
940                                                           "(objectclass=ds-cfg-local-db-backend)");
941        try (final EntryReader entryReader = searchConfigFile(sr)) {
942          // Abort the upgrade if there are JE backends but no JE library.
943          if (entryReader.hasNext() && !isJeLibraryAvailable()) {
944            throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, INFO_UPGRADE_TASK_MIGRATE_JE_NO_JE_LIB.get());
945          }
946          while (entryReader.hasNext()) {
947            Backend backend = new Backend(entryReader.readEntry());
948            if (backend.isEnabled) {
949              abortIfBackendCannotBeMigrated(backend);
950            }
951            backends.add(backend);
952          }
953        } catch (IOException e) {
954          throw new ClientException(ReturnCode.APPLICATION_ERROR, INFO_UPGRADE_TASK_MIGRATE_CONFIG_READ_FAIL.get(), e);
955        }
956      }
957
958      private void abortIfBackendCannotBeMigrated(final Backend backend) throws ClientException {
959        Set<String> existingDatabases = JEHelper.listDatabases(backend.envDir);
960        for (DN baseDN : backend.baseDNs) {
961          final String oldName = oldName(baseDN);
962          if (!existingDatabases.contains(oldName)) {
963            LocalizableMessage msg = INFO_UPGRADE_TASK_MIGRATE_JE_UGLY_DN.get(backend.id, baseDN);
964            throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg);
965          }
966        }
967      }
968
969      /**
970       * Renames the compressed schema indexes and id2entry in a 2.x environment to
971       * the naming scheme used in 3.0.0. Before 3.0.0 JE databases were named as follows:
972       *
973       * 1) normalize the base DN
974       * 2) replace all non-alphanumeric characters with '_'
975       * 3) append '_'
976       * 4) append the index name.
977       *
978       * For example, id2entry in the base DN dc=white space,dc=com would be named
979       * dc_white_space_dc_com_id2entry. In 3.0.0 JE databases are named as follows:
980       *
981       * 1) normalize the base DN and URL encode it (' '  are converted to %20)
982       * 2) format as '/' + URL encoded base DN + '/' + index name.
983       *
984       * The matching rules in 3.0.0 are not compatible with previous versions, so we need
985       * to do a best effort attempt to figure out the old database name from a given base DN.
986       */
987      @Override
988      public void perform(final UpgradeContext context) throws ClientException {
989        if (!isJeLibraryAvailable()) {
990          return;
991        }
992
993        for (Backend backend : backends) {
994          if (backend.isEnabled) {
995            ProgressNotificationCallback pnc = new ProgressNotificationCallback(
996                    INFORMATION, INFO_UPGRADE_TASK_MIGRATE_JE_SUMMARY_1.get(backend.id), 0);
997            context.notifyProgress(pnc);
998            try {
999              JEHelper.migrateDatabases(backend.envDir, backend.renamedDbs);
1000              context.notifyProgress(pnc.setProgress(100));
1001            } catch (ClientException e) {
1002              manageTaskException(context, e.getMessageObject(), pnc);
1003            }
1004          } else {
1005            // Skip backends which have been disabled.
1006            final ProgressNotificationCallback pnc = new ProgressNotificationCallback(
1007                    INFORMATION, INFO_UPGRADE_TASK_MIGRATE_JE_SUMMARY_5.get(backend.id), 0);
1008            context.notifyProgress(pnc);
1009            context.notifyProgress(pnc.setProgress(100));
1010          }
1011        }
1012      }
1013
1014      private boolean isJeLibraryAvailable() {
1015        return isClassAvailable("com.sleepycat.je.Environment");
1016      }
1017
1018      private String newName(final DN baseDN) {
1019        return new TreeName(baseDN.toNormalizedUrlSafeString(), "id2entry").toString();
1020      }
1021
1022      private String oldName(final DN baseDN) {
1023        String s = baseDN.toString();
1024        StringBuilder builder = new StringBuilder();
1025        for (int i = 0; i < s.length(); i++) {
1026          char c = s.charAt(i);
1027          builder.append(Character.isLetterOrDigit(c) ? c : '_');
1028        }
1029        builder.append("_id2entry");
1030        return builder.toString();
1031      }
1032
1033      @Override
1034      public String toString()
1035      {
1036        return INFO_UPGRADE_TASK_MIGRATE_JE_SUMMARY_1.get("%s").toString();
1037      }
1038    };
1039  }
1040
1041  /**
1042   * Creates backups of the local DB backends directories by renaming adding them a ".bak" suffix.
1043   *  e.g "userRoot" would become "userRoot.bak"
1044   */
1045  static UpgradeTask renameLocalDBBackendDirectories()
1046  {
1047    return new AbstractUpgradeTask()
1048    {
1049      private boolean reimportRequired;
1050
1051      @Override
1052      public void perform(UpgradeContext context) throws ClientException
1053      {
1054        try
1055        {
1056          Filter filter = Filter.equality("objectclass", "ds-cfg-local-db-backend");
1057          SearchRequest findLocalDBBackends = Requests.newSearchRequest(DN.rootDN(), SearchScope.WHOLE_SUBTREE, filter);
1058          try (final EntryReader jeBackends = searchConfigFile(findLocalDBBackends))
1059          {
1060            while (jeBackends.hasNext())
1061            {
1062              Upgrade.needToRunPostUpgradePhase();
1063              reimportRequired = true;
1064
1065              Entry jeBackend = jeBackends.readEntry();
1066              File dbParent = UpgradeUtils.getFileForPath(jeBackend.parseAttribute("ds-cfg-db-directory").asString());
1067              String id = jeBackend.parseAttribute("ds-cfg-backend-id").asString();
1068
1069              // Use canonical paths so that the progress message is more readable.
1070              File dbDirectory = new File(dbParent, id).getCanonicalFile();
1071              File dbDirectoryBackup = new File(dbParent, id + ".bak").getCanonicalFile();
1072              if (dbDirectory.exists() && !dbDirectoryBackup.exists())
1073              {
1074                LocalizableMessage msg = INFO_UPGRADE_TASK_RENAME_JE_DB_DIR.get(dbDirectory, dbDirectoryBackup);
1075                ProgressNotificationCallback pnc = new ProgressNotificationCallback(0, msg, 0);
1076                context.notifyProgress(pnc);
1077                boolean renameSucceeded = dbDirectory.renameTo(dbDirectoryBackup);
1078                context.notifyProgress(pnc.setProgress(renameSucceeded ? 100 : -1));
1079              }
1080            }
1081          }
1082        }
1083        catch (Exception e)
1084        {
1085          logger.error(LocalizableMessage.raw(e.getMessage()));
1086        }
1087      }
1088
1089      @Override
1090      public void postUpgrade(UpgradeContext context) throws ClientException
1091      {
1092        postponePostUpgrade(context);
1093      }
1094
1095      @Override
1096      public void postponePostUpgrade(UpgradeContext context) throws ClientException
1097      {
1098        if (reimportRequired)
1099        {
1100          context.notify(INFO_UPGRADE_TASK_RENAME_JE_DB_DIR_WARNING.get(), TextOutputCallback.WARNING);
1101        }
1102      }
1103
1104      @Override
1105      public String toString()
1106      {
1107        return INFO_UPGRADE_TASK_RENAME_JE_DB_DIR.get("%s", "%s").toString();
1108      }
1109    };
1110  }
1111
1112  /** This inner classes causes JE to be lazily linked and prevents runtime errors if JE is not in the classpath. */
1113  static final class JEHelper {
1114    private static ClientException clientException(final File backendDirectory, final DatabaseException e) {
1115      logger.error(LocalizableMessage.raw(StaticUtils.stackTraceToString(e)));
1116      return new ClientException(ReturnCode.CONSTRAINT_VIOLATION,
1117                                 INFO_UPGRADE_TASK_MIGRATE_JE_ENV_UNREADABLE.get(backendDirectory), e);
1118    }
1119
1120    static Set<String> listDatabases(final File backendDirectory) throws ClientException {
1121      try (Environment je = new Environment(backendDirectory, null)) {
1122        Set<String> databases = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
1123        databases.addAll(je.getDatabaseNames());
1124        return databases;
1125      } catch (DatabaseException e) {
1126        throw clientException(backendDirectory, e);
1127      }
1128    }
1129
1130    static void migrateDatabases(final File envDir, final Map<String, String> renamedDbs) throws ClientException {
1131      EnvironmentConfig config = new EnvironmentConfig().setTransactional(true);
1132      try (Environment je = new Environment(envDir, config)) {
1133        final Transaction txn = je.beginTransaction(null, new TransactionConfig());
1134        try {
1135          for (String dbName : je.getDatabaseNames()) {
1136            String newDbName = renamedDbs.get(dbName);
1137            if (newDbName != null) {
1138              // id2entry or compressed schema should be kept
1139              je.renameDatabase(txn, dbName, newDbName);
1140            } else {
1141              // This index will need rebuilding
1142              je.removeDatabase(txn, dbName);
1143            }
1144          }
1145          txn.commit();
1146        } finally {
1147          txn.abort();
1148        }
1149      } catch (DatabaseException e) {
1150        throw JEHelper.clientException(envDir, e);
1151      }
1152    }
1153  }
1154
1155  private static void displayChangeCount(final String fileName,
1156      final int changeCount)
1157  {
1158    if (changeCount != 0)
1159    {
1160      logger.debug(INFO_UPGRADE_CHANGE_DONE_IN_SPECIFIC_FILE, fileName, changeCount);
1161    }
1162    else
1163    {
1164      logger.debug(INFO_UPGRADE_NO_CHANGE_DONE_IN_SPECIFIC_FILE, fileName);
1165    }
1166  }
1167
1168  private static void displayTaskLogInformation(final LocalizableMessage summary,
1169      final String filter, final String... ldif)
1170  {
1171    logger.debug(summary);
1172    if (filter != null)
1173    {
1174      logger.debug(LocalizableMessage.raw(filter));
1175    }
1176    if (ldif != null)
1177    {
1178      logger.debug(LocalizableMessage.raw(Arrays.toString(ldif)));
1179    }
1180  }
1181
1182  private static void manageTaskException(final UpgradeContext context,
1183      final LocalizableMessage message, final ProgressNotificationCallback pnc)
1184      throws ClientException
1185  {
1186    countErrors++;
1187    context.notifyProgress(pnc.setProgress(-100));
1188    logger.error(message);
1189    if (!context.isIgnoreErrorsMode())
1190    {
1191      throw new ClientException(ReturnCode.ERROR_UNEXPECTED, message);
1192    }
1193  }
1194
1195  private static UpgradeTask updateConfigEntry(final LocalizableMessage summary, final String filter,
1196      final ChangeOperationType changeOperationType, final String... ldif)
1197  {
1198    return new AbstractUpgradeTask()
1199    {
1200      @Override
1201      public void perform(final UpgradeContext context) throws ClientException
1202      {
1203        performConfigFileUpdate(summary, filter, changeOperationType, context, ldif);
1204      }
1205
1206      @Override
1207      public String toString()
1208      {
1209        return String.valueOf(summary);
1210      }
1211    };
1212  }
1213
1214  private static void performConfigFileUpdate(final LocalizableMessage summary, final String filter,
1215      final ChangeOperationType changeOperationType, final UpgradeContext context, final String... ldif)
1216      throws ClientException
1217  {
1218    displayTaskLogInformation(summary, filter, ldif);
1219
1220    final ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, summary, 20);
1221    context.notifyProgress(pnc);
1222
1223    try
1224    {
1225      final File configFile = getConfigLdifFile();
1226
1227      final Filter filterVal = filter != null ? Filter.valueOf(filter) : null;
1228      final int changeCount = updateConfigFile(
1229          configFile.getPath(), filterVal, changeOperationType, ldif);
1230
1231      displayChangeCount(configFile.getPath(), changeCount);
1232
1233      context.notifyProgress(pnc.setProgress(100));
1234    }
1235    catch (final Exception e)
1236    {
1237      manageTaskException(context, LocalizableMessage.raw(e.getMessage()), pnc);
1238    }
1239  }
1240
1241  private static File getConfigLdifFile()
1242  {
1243    return new File(configDirectory, CURRENT_CONFIG_FILE_NAME);
1244  }
1245
1246  static UpgradeTask clearReplicationDbDirectory()
1247  {
1248    return new AbstractUpgradeTask()
1249    {
1250      private File replicationDbDir;
1251
1252      @Override
1253      public void prepare(final UpgradeContext context) throws ClientException
1254      {
1255        String replDbDir = readReplicationDbDirFromConfig();
1256        if (replDbDir != null
1257            && context.confirmYN(INFO_UPGRADE_TASK_MIGRATE_CHANGELOG_DESCRIPTION.get(), NO) == YES)
1258        {
1259          replicationDbDir = new File(getInstancePath(), replDbDir).getAbsoluteFile();
1260        }
1261        // if replDbDir == null, then this is not an RS, there is no changelog DB to clear
1262      }
1263
1264      private String readReplicationDbDirFromConfig() throws ClientException
1265      {
1266        final SearchRequest sr = Requests.newSearchRequest(
1267            DN.valueOf("cn=replication server,cn=Multimaster Synchronization,cn=Synchronization Providers,cn=config"),
1268            SearchScope.BASE_OBJECT, Filter.alwaysTrue());
1269        try (final EntryReader entryReader = searchConfigFile(sr))
1270        {
1271          if (entryReader.hasNext())
1272          {
1273            final Entry replServerCfg = entryReader.readEntry();
1274            return replServerCfg.parseAttribute("ds-cfg-replication-db-directory").asString();
1275          }
1276          return null;
1277        }
1278        catch (IOException e)
1279        {
1280          LocalizableMessage msg = INFO_UPGRADE_TASK_MIGRATE_CONFIG_READ_FAIL.get();
1281          throw new ClientException(ReturnCode.APPLICATION_ERROR, msg, e);
1282        }
1283      }
1284
1285      @Override
1286      public void perform(final UpgradeContext context) throws ClientException
1287      {
1288        if (replicationDbDir == null)
1289        {
1290          // there is no changelog DB to clear
1291          return;
1292        }
1293
1294        LocalizableMessage msg = INFO_UPGRADE_TASK_DELETE_CHANGELOG_SUMMARY.get(replicationDbDir);
1295        ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, msg, 0);
1296        context.notifyProgress(pnc);
1297        try
1298        {
1299          FileManager.deleteRecursively(replicationDbDir);
1300          context.notifyProgress(pnc.setProgress(100));
1301        }
1302        catch (ClientException e)
1303        {
1304          manageTaskException(context, e.getMessageObject(), pnc);
1305        }
1306        catch (Exception e)
1307        {
1308          manageTaskException(context, LocalizableMessage.raw(e.getLocalizedMessage()), pnc);
1309        }
1310      }
1311
1312      @Override
1313      public String toString()
1314      {
1315        return INFO_UPGRADE_TASK_DELETE_CHANGELOG_SUMMARY.get(replicationDbDir).toString();
1316      }
1317    };
1318  }
1319
1320  /** Removes server and localized jars from previous version since names have changed. */
1321  static UpgradeTask removeOldJarFiles()
1322  {
1323    return new AbstractUpgradeTask()
1324    {
1325
1326      @Override
1327      public void perform(final UpgradeContext context) throws ClientException
1328      {
1329        final ProgressNotificationCallback pnc = new ProgressNotificationCallback(
1330            INFORMATION, INFO_UPGRADE_TASK_REMOVE_OLD_JARS.get(), 0);
1331        context.notifyProgress(pnc);
1332
1333        final boolean fileSystemIsCaseSensitive = fileSystemIsCaseSensitive(context);
1334
1335        deleteJarFilesIfFileSystemIsCaseSensitive(fileSystemIsCaseSensitive, "OpenDJ");
1336        for (final String locale : SUPPORTED_LOCALES_FOR_3_0_0)
1337        {
1338          deleteJarFiles("OpenDJ-" + locale);
1339          deleteJarFilesIfFileSystemIsCaseSensitive(fileSystemIsCaseSensitive, "OpenDJ_" + locale);
1340        }
1341        // Jar files from 2.6.x
1342        deleteJarFiles("jackson-core-asl", "jackson-mapper-asl", "json-fluent", "json-resource-servlet",
1343            "mail", "opendj-ldap-sdk", "opendj-rest2ldap-servlet", "opendj-server2x-adapter");
1344        context.notifyProgress(pnc.setProgress(100));
1345      }
1346
1347      private void deleteJarFilesIfFileSystemIsCaseSensitive(
1348          final boolean fileSystemIsCaseSensitive, final String... jarFileNames)
1349      {
1350        if (fileSystemIsCaseSensitive)
1351        {
1352          deleteJarFiles(jarFileNames);
1353        }
1354      }
1355
1356      private void deleteJarFiles(final String... jarFileNames)
1357      {
1358        for (final String jarFileName : jarFileNames)
1359        {
1360          final File f = new File(libDirectory, jarFileName + ".jar");
1361          if (f.exists())
1362          {
1363            f.delete();
1364          }
1365        }
1366      }
1367
1368      /** Used to know if we have to remove old "camel case" OpenDJ[_]*.jar(see OPENDJ-2692). */
1369      private boolean fileSystemIsCaseSensitive(final UpgradeContext context) throws ClientException
1370      {
1371        final File openDJCamelCaseJar = new File(libDirectory, "OpenDJ.jar");
1372        try
1373        {
1374          // getCanonicalPath() will return the new "opendj.jar" on case insensitive file systems
1375          return openDJCamelCaseJar.getCanonicalPath().equals(openDJCamelCaseJar.getAbsolutePath());
1376        }
1377        catch (final IOException unlikely)
1378        {
1379          // Warn the user that he may have some old camel case jars to remove
1380          context.notify(INFO_UPGRADE_TASK_UNABLE_TO_REMOVE_OLD_JARS.get());
1381          Upgrade.needToExitWithErrorCode();
1382          return false;
1383        }
1384      }
1385    };
1386
1387  }
1388
1389  /** Prevent instantiation. */
1390  private UpgradeTasks()
1391  {
1392    // Do nothing.
1393  }
1394}