001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Portions Copyright 2013-2015 ForgeRock AS
025 */
026package org.opends.server.tools.upgrade;
027
028import java.io.File;
029import java.io.IOException;
030import java.util.Arrays;
031import java.util.HashSet;
032import java.util.LinkedList;
033import java.util.List;
034import java.util.Set;
035
036import javax.security.auth.callback.TextOutputCallback;
037
038import org.forgerock.i18n.LocalizableMessage;
039import org.forgerock.i18n.slf4j.LocalizedLogger;
040import org.forgerock.opendj.ldap.Filter;
041import org.opends.server.tools.JavaPropertiesTool;
042import org.opends.server.tools.RebuildIndex;
043import org.opends.server.util.BuildVersion;
044import org.opends.server.util.ChangeOperationType;
045
046import com.forgerock.opendj.cli.ClientException;
047import com.forgerock.opendj.cli.ReturnCode;
048
049import static javax.security.auth.callback.ConfirmationCallback.*;
050import static org.opends.messages.ToolMessages.*;
051import static org.opends.server.tools.upgrade.FileManager.*;
052import static org.opends.server.tools.upgrade.Installation.*;
053import static org.opends.server.tools.upgrade.UpgradeUtils.*;
054
055/**
056 * Factory methods for create new upgrade tasks.
057 */
058public final class UpgradeTasks
059{
060  /** Logger for the upgrade. */
061  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
062
063  /** An errors counter in case of ignore errors mode. */
064  static int countErrors;
065
066  /** Contains all the indexes to rebuild. */
067  static Set<String> indexesToRebuild = new HashSet<>();
068
069  /** A flag to avoid rebuild single indexes if 'rebuild all' is selected. */
070  static boolean isRebuildAllIndexesIsPresent;
071
072  /** A flag for marking 'rebuild all' task accepted by user. */
073  static boolean isRebuildAllIndexesTaskAccepted;
074
075  /**
076   * Returns a new upgrade task which adds a config entry to the underlying
077   * config file.
078   *
079   * @param summary
080   *          The summary of this upgrade task.
081   * @param ldif
082   *          The LDIF record which will be applied to matching entries.
083   * @return A new upgrade task which applies an LDIF record to all
084   *         configuration entries matching the provided filter.
085   */
086  public static UpgradeTask addConfigEntry(final LocalizableMessage summary,
087      final String... ldif)
088  {
089    return updateConfigEntry(summary, null, ChangeOperationType.ADD, ldif);
090  }
091
092  /**
093   * This task copies the file placed in parameter within the config / schema
094   * folder. If the file already exists, it's overwritten.
095   *
096   * @param fileName
097   *          The name of the file which need to be copied.
098   * @return A task which copy the the file placed in parameter within the
099   *         config / schema folder. If the file already exists, it's
100   *         overwritten.
101   */
102  public static UpgradeTask copySchemaFile(final String fileName)
103  {
104    return new AbstractUpgradeTask()
105    {
106      @Override
107      public void perform(final UpgradeContext context) throws ClientException
108      {
109        final LocalizableMessage msg = INFO_UPGRADE_TASK_REPLACE_SCHEMA_FILE.get(fileName);
110        logger.debug(msg);
111
112        final ProgressNotificationCallback pnc =
113            new ProgressNotificationCallback(0, msg, 0);
114
115        final File schemaFileTemplate =
116            new File(templateConfigSchemaDirectory, fileName);
117
118        try
119        {
120          context.notifyProgress(pnc.setProgress(20));
121          if (!schemaFileTemplate.exists() || schemaFileTemplate.length() == 0)
122          {
123            throw new IOException(ERR_UPGRADE_CORRUPTED_TEMPLATE
124                .get(schemaFileTemplate.getPath()).toString());
125          }
126          copy(schemaFileTemplate, configSchemaDirectory, true);
127          context.notifyProgress(pnc.setProgress(100));
128        }
129        catch (final IOException e)
130        {
131          manageTaskException(context, ERR_UPGRADE_COPYSCHEMA_FAILS.get(
132              schemaFileTemplate.getName(), e.getMessage()), pnc);
133        }
134      }
135    };
136  }
137
138  /**
139   * This task copies the file placed in parameter within the config folder. If
140   * the file already exists, it's overwritten.
141   *
142   * @param fileName
143   *          The name of the file which need to be copied.
144   * @return A task which copy the the file placed in parameter within the
145   *         config folder. If the file already exists, it's overwritten.
146   */
147  public static UpgradeTask addConfigFile(final String fileName)
148  {
149    return new AbstractUpgradeTask()
150    {
151      @Override
152      public void perform(final UpgradeContext context) throws ClientException
153      {
154        final LocalizableMessage msg = INFO_UPGRADE_TASK_ADD_CONFIG_FILE.get(fileName);
155        logger.debug(msg);
156
157        final ProgressNotificationCallback pnc =
158            new ProgressNotificationCallback(0, msg, 0);
159
160        final File configFile = new File(templateConfigDirectory, fileName);
161
162        try
163        {
164          context.notifyProgress(pnc.setProgress(20));
165
166          copy(configFile, configDirectory, true);
167          context.notifyProgress(pnc.setProgress(100));
168        }
169        catch (final IOException e)
170        {
171          manageTaskException(context, ERR_UPGRADE_ADD_CONFIG_FILE_FAILS.get(
172              configFile.getName(), e.getMessage()), pnc);
173        }
174      }
175    };
176  }
177
178  /**
179   * Returns a new upgrade task which deletes a config entry from the underlying
180   * config file.
181   *
182   * @param summary
183   *          The summary of this upgrade task.
184   * @param dnInLDIF
185   *          The dn to delete in the form of LDIF.
186   * @return A new upgrade task which applies an LDIF record to all
187   *         configuration entries matching the provided filter.
188   */
189  public static UpgradeTask deleteConfigEntry(final LocalizableMessage summary,
190      final String dnInLDIF)
191  {
192    return updateConfigEntry(summary, null, ChangeOperationType.DELETE, dnInLDIF);
193  }
194
195  /**
196   * Returns a new upgrade task which applies an LDIF record to all
197   * configuration entries matching the provided filter.
198   *
199   * @param summary
200   *          The summary of this upgrade task.
201   * @param filter
202   *          The LDAP filter which configuration entries must match.
203   * @param ldif
204   *          The LDIF record which will be applied to matching entries.
205   * @return A new upgrade task which applies an LDIF record to all
206   *         configuration entries matching the provided filter.
207   */
208  public static UpgradeTask modifyConfigEntry(final LocalizableMessage summary,
209      final String filter, final String... ldif)
210  {
211    return updateConfigEntry(summary, filter, ChangeOperationType.MODIFY, ldif);
212  }
213
214  /**
215   * This task adds a new attribute type (must exists in the original file) to
216   * the specified file placed in parameter. The destination must be a file
217   * contained in the config/schema folder. E.g : This example adds a new
218   * attribute type named 'etag' in the 00.core.ldif. The 'etag' attribute
219   * already exists in the 00-core.ldif template schema file.
220   *
221   * <pre>
222   * register(&quot;2.5.0.7192&quot;,
223   *   newAttributeTypes(LocalizableMessage.raw(&quot;New attribute etag&quot;),
224   *   false, &quot;00-core.ldif&quot;,
225   *   &quot;1.3.6.1.4.1.36733.2.1.1.59&quot;));
226   * </pre>
227   *
228   * @param summary
229   *          The summary of the task.
230   * @param fileName
231   *          The file where to add the new attribute types. This file must be
232   *          contained in the configuration/schema folder.
233   * @param attributeOids
234   *          The OIDs of the new attributes to add to.
235   * @return An upgrade task which adds new attribute types, defined previously
236   *         in the configuration template files, reads the definition
237   *         and adds it onto the specified file in parameter.
238   */
239  public static UpgradeTask newAttributeTypes(final LocalizableMessage summary,
240      final String fileName, final String... attributeOids)
241  {
242    return new AbstractUpgradeTask()
243    {
244      @Override
245      public void perform(final UpgradeContext context) throws ClientException
246      {
247        logger.debug(summary);
248
249        final ProgressNotificationCallback pnc =
250            new ProgressNotificationCallback(0, summary, 20);
251        context.notifyProgress(pnc);
252
253        final File schemaFileTemplate =
254            new File(templateConfigSchemaDirectory, fileName);
255
256        final File pathDestination = new File(configSchemaDirectory, fileName);
257        try
258        {
259          final int changeCount =
260              updateSchemaFile(schemaFileTemplate, pathDestination,
261                  attributeOids, null);
262
263          displayChangeCount(pathDestination.getPath(), changeCount);
264
265          context.notifyProgress(pnc.setProgress(100));
266        }
267        catch (final IOException | IllegalStateException e)
268        {
269          manageTaskException(context, ERR_UPGRADE_ADDATTRIBUTE_FAILS.get(
270              schemaFileTemplate.getName(), e.getMessage()), pnc);
271        }
272      }
273    };
274  }
275
276  /**
277   * This task adds a new object class (must exists in the original file) to the
278   * specified file placed in parameter. The destination must be a file
279   * contained in the config/schema folder.
280   *
281   * @param summary
282   *          The summary of the task.
283   * @param fileName
284   *          The file where to add the new object classes. This file must be
285   *          contained in the configuration/schema folder.
286   * @param objectClassesOids
287   *          The OIDs of the new object classes to add to.
288   * @return An upgrade task which adds new object classes, defined previously
289   *         in the configuration template files,
290   *         reads the definition and adds it onto the specified file in
291   *         parameter.
292   */
293  public static UpgradeTask newObjectClasses(final LocalizableMessage summary,
294      final String fileName, final String... objectClassesOids)
295  {
296    return new AbstractUpgradeTask()
297    {
298      @Override
299      public void perform(final UpgradeContext context) throws ClientException
300      {
301        logger.debug(summary);
302
303        final ProgressNotificationCallback pnc =
304            new ProgressNotificationCallback(0, summary, 20);
305        context.notifyProgress(pnc);
306
307        final File schemaFileTemplate =
308            new File(templateConfigSchemaDirectory, fileName);
309
310        final File pathDestination = new File(configSchemaDirectory, fileName);
311
312        context.notifyProgress(pnc.setProgress(20));
313
314        try
315        {
316          final int changeCount =
317              updateSchemaFile(schemaFileTemplate, pathDestination,
318                  null, objectClassesOids);
319
320          displayChangeCount(pathDestination.getPath(), changeCount);
321
322          context.notifyProgress(pnc.setProgress(100));
323        }
324        catch (final IOException e)
325        {
326          manageTaskException(context, ERR_UPGRADE_ADDOBJECTCLASS_FAILS.get(
327              schemaFileTemplate.getName(), e.getMessage()), pnc);
328        }
329        catch (final IllegalStateException e)
330        {
331          manageTaskException(context, ERR_UPGRADE_ADDATTRIBUTE_FAILS.get(
332              schemaFileTemplate.getName(), e.getMessage()), pnc);
333        }
334      }
335    };
336  }
337
338  /**
339   * Re-run the dsjavaproperties tool to rewrite the set-java-home script/batch file.
340   *
341   * @param summary
342   *          The summary of the task.
343   * @return An upgrade task which runs dsjavaproperties.
344   */
345  public static UpgradeTask rerunJavaPropertiesTool(final LocalizableMessage summary)
346  {
347    return new AbstractUpgradeTask()
348    {
349      @Override
350      public void perform(UpgradeContext context) throws ClientException
351      {
352        logger.debug(summary);
353
354        final ProgressNotificationCallback pnc = new ProgressNotificationCallback(0, summary, 50);
355        context.notifyProgress(pnc);
356
357        int returnValue = JavaPropertiesTool.mainCLI("--quiet");
358        context.notifyProgress(pnc.setProgress(100));
359
360        if (JavaPropertiesTool.ErrorReturnCode.SUCCESSFUL.getReturnCode() != returnValue &&
361                JavaPropertiesTool.ErrorReturnCode.SUCCESSFUL_NOP.getReturnCode() != returnValue) {
362          throw new ClientException(ReturnCode.ERROR_UNEXPECTED, ERR_UPGRADE_DSJAVAPROPERTIES_FAILED.get());
363        }
364      }
365    };
366  }
367
368  /**
369   * Creates a group of tasks which will only be invoked if the current version
370   * is more recent than the provided version. This may be useful in cases where
371   * a regression was introduced in version X and resolved in a later version Y.
372   * In this case, the provided upgrade tasks will only be invoked if the
373   * current version is between X (inclusive) and Y (exclusive).
374   *
375   * @param versionString
376   *          The lower bound version. The upgrade tasks will not be applied if
377   *          the current version is older than this version.
378   * @param tasks
379   *          The group of tasks to invoke if the current version is equal to or
380   *          more recent than {@code versionString}.
381   * @return An upgrade task which will only be invoked if the current version
382   *         is more recent than the provided version.
383   */
384  public static UpgradeTask regressionInVersion(final String versionString, final UpgradeTask... tasks)
385  {
386    final BuildVersion version = BuildVersion.valueOf(versionString);
387    return conditionalUpgradeTasks(new UpgradeCondition()
388    {
389      @Override
390      public boolean shouldPerformUpgradeTasks(final UpgradeContext context) throws ClientException
391      {
392        return context.getFromVersion().compareTo(version) >= 0;
393      }
394    }, tasks);
395  }
396
397  /**
398   * Creates a group of tasks which will only be invoked if the user confirms agreement. This may be
399   * useful in cases where a feature is deprecated and the upgrade is capable of migrating the
400   * configuration to the new replacement feature.
401   *
402   * @param message
403   *          The confirmation message.
404   * @param tasks
405   *          The group of tasks to invoke if the user agrees.
406   * @return An upgrade task which will only be invoked if the user confirms agreement.
407   */
408  public static UpgradeTask requireConfirmation(final LocalizableMessage message, final UpgradeTask... tasks)
409  {
410    return conditionalUpgradeTasks(new UpgradeCondition()
411    {
412      @Override
413      public boolean shouldPerformUpgradeTasks(final UpgradeContext context) throws ClientException
414      {
415        return context.confirmYN(INFO_UPGRADE_TASK_NEEDS_USER_CONFIRM.get(message), YES) == YES;
416      }
417    }, tasks);
418  }
419
420  /**
421   * Determines whether conditional tasks should be performed.
422   */
423  private static interface UpgradeCondition
424  {
425    boolean shouldPerformUpgradeTasks(final UpgradeContext context) throws ClientException;
426  }
427
428  private static UpgradeTask conditionalUpgradeTasks(final UpgradeCondition condition, final UpgradeTask... tasks)
429  {
430    return new AbstractUpgradeTask()
431    {
432      private boolean shouldPerformUpgradeTasks = true;
433
434      @Override
435      public void prepare(final UpgradeContext context) throws ClientException
436      {
437        shouldPerformUpgradeTasks = condition.shouldPerformUpgradeTasks(context);
438        if (shouldPerformUpgradeTasks)
439        {
440          for (UpgradeTask task : tasks)
441          {
442            task.prepare(context);
443          }
444        }
445      }
446
447      @Override
448      public void perform(final UpgradeContext context) throws ClientException
449      {
450        if (shouldPerformUpgradeTasks)
451        {
452          for (UpgradeTask task : tasks)
453          {
454            task.perform(context);
455          }
456        }
457      }
458
459      @Override
460      public void postUpgrade(UpgradeContext context) throws ClientException
461      {
462        if (shouldPerformUpgradeTasks)
463        {
464          boolean isOk = true;
465          for (final UpgradeTask task : tasks)
466          {
467            if (isOk)
468            {
469              try
470              {
471                task.postUpgrade(context);
472              }
473              catch (ClientException e)
474              {
475                logger.error(LocalizableMessage.raw(e.getMessage()));
476                isOk = false;
477              }
478            }
479            else
480            {
481              task.postponePostUpgrade(context);
482            }
483          }
484        }
485      }
486
487    };
488  }
489
490  /**
491   * Creates a rebuild all indexes task.
492   *
493   * @param summary
494   *          The summary of this upgrade task.
495   * @return An Upgrade task which rebuild all the indexes.
496   */
497  public static UpgradeTask rebuildAllIndexes(final LocalizableMessage summary)
498  {
499    return new AbstractUpgradeTask()
500    {
501      private boolean isATaskToPerform = false;
502
503      @Override
504      public void prepare(UpgradeContext context) throws ClientException
505      {
506        Upgrade.setHasPostUpgradeTask(true);
507        // Requires answer from the user.
508        isATaskToPerform = context.confirmYN(summary, NO) == YES;
509        isRebuildAllIndexesIsPresent = true;
510        isRebuildAllIndexesTaskAccepted = isATaskToPerform;
511      }
512
513      @Override
514      public void postUpgrade(final UpgradeContext context) throws ClientException
515      {
516        if (!isATaskToPerform)
517        {
518          postponePostUpgrade(context);
519        }
520      }
521
522      @Override
523      public void postponePostUpgrade(UpgradeContext context) throws ClientException
524      {
525        context.notify(INFO_UPGRADE_ALL_REBUILD_INDEX_DECLINED.get(), TextOutputCallback.WARNING);
526      }
527    };
528  }
529
530
531
532  /**
533   * Creates a rebuild index task for a given single index. As this task is
534   * possibly lengthy, it's considered as a post upgrade task. This task is not
535   * mandatory; e.g not require user interaction, but could be required to get a
536   * fully functional server. <br />
537   * The post upgrade task just register the task. The rebuild indexes tasks are
538   * completed at the end of the upgrade process.
539   *
540   * @param summary
541   *          A message describing why the index needs to be rebuilt and asking
542   *          them whether or not they wish to perform this task after the
543   *          upgrade.
544   * @param index
545   *          The index to rebuild.
546   * @return The rebuild index task.
547   */
548  public static UpgradeTask rebuildSingleIndex(final LocalizableMessage summary,
549      final String index)
550  {
551    return new AbstractUpgradeTask()
552    {
553      private boolean isATaskToPerform = false;
554
555      @Override
556      public void prepare(UpgradeContext context) throws ClientException
557      {
558        Upgrade.setHasPostUpgradeTask(true);
559        // Requires answer from the user.
560        isATaskToPerform = context.confirmYN(summary, NO) == YES;
561      }
562
563      @Override
564      public void postUpgrade(final UpgradeContext context) throws ClientException
565      {
566        if (isATaskToPerform)
567        {
568          indexesToRebuild.add(index);
569        }
570        else
571        {
572          postponePostUpgrade(context);
573        }
574      }
575
576      @Override
577      public void postponePostUpgrade(UpgradeContext context) throws ClientException
578      {
579        if (!isRebuildAllIndexesIsPresent)
580        {
581          context.notify(INFO_UPGRADE_REBUILD_INDEX_DECLINED.get(index), TextOutputCallback.WARNING);
582        }
583      }
584    };
585  }
586
587  /**
588   * This task is processed at the end of the upgrade, rebuilding indexes. If a
589   * rebuild all indexes has been registered before, it takes the flag
590   * relatively to single rebuild index.
591   *
592   * @return The post upgrade rebuild indexes task.
593   */
594  public static UpgradeTask postUpgradeRebuildIndexes()
595  {
596    return new AbstractUpgradeTask()
597    {
598      @Override
599      public void postUpgrade(final UpgradeContext context) throws ClientException
600      {
601        LocalizableMessage message = null;
602        final List<String> args = new LinkedList<>();
603
604        if (isRebuildAllIndexesIsPresent && isRebuildAllIndexesTaskAccepted)
605        {
606          args.add("--rebuildAll");
607          message = INFO_UPGRADE_REBUILD_ALL.get();
608        }
609        else if (!indexesToRebuild.isEmpty()
610            && !isRebuildAllIndexesTaskAccepted)
611        {
612          message = INFO_UPGRADE_REBUILD_INDEX_STARTS.get(indexesToRebuild);
613
614          // Adding all requested indexes.
615          for (final String indexToRebuild : indexesToRebuild)
616          {
617            args.add("-i");
618            args.add(indexToRebuild);
619          }
620        }
621        else
622        {
623          return;
624        }
625        // Startup message.
626        ProgressNotificationCallback pnc =
627            new ProgressNotificationCallback(0, message, 25);
628        logger.debug(message);
629        context.notifyProgress(pnc);
630
631        // Sets the arguments like the rebuild index command line.
632        args.addAll(Arrays.asList(
633            "-f",
634            new File(configDirectory, CURRENT_CONFIG_FILE_NAME).getAbsolutePath()));
635
636        /*
637         * Index(es) could be contained in several backends or none, If none,
638         * the post upgrade tasks succeed and a message is printed in the
639         * upgrade log file.
640         */
641        final List<String> backends = UpgradeUtils.getLocalBackendsFromConfig();
642        if (!backends.isEmpty())
643        {
644          for (final String be : backends)
645          {
646            args.add("-b");
647            args.add(be);
648          }
649
650          // Displays info about command line args for log only.
651          logger.debug(INFO_UPGRADE_REBUILD_INDEX_ARGUMENTS, args);
652
653          /*
654           * The rebuild-index process just display a status ok / fails. The
655           * logger stream contains all the log linked to this process. The
656           * complete process is not displayed in the upgrade console.
657           */
658          final String[] commandLineArgs = args.toArray(new String[args.size()]);
659          final int result = new RebuildIndex().rebuildIndexesWithinMultipleBackends(
660              true, UpgradeLog.getPrintStream(), commandLineArgs);
661
662          if (result == 0)
663          {
664            logger.debug(INFO_UPGRADE_REBUILD_INDEX_ENDS);
665            context.notifyProgress(pnc.setProgress(100));
666          }
667          else
668          {
669            final LocalizableMessage msg = ERR_UPGRADE_PERFORMING_POST_TASKS_FAIL.get();
670            context.notifyProgress(pnc.setProgress(-100));
671            throw new ClientException(ReturnCode.ERROR_UNEXPECTED, msg);
672          }
673        }
674        else
675        {
676          logger.debug(INFO_UPGRADE_REBUILD_INDEX_NO_BACKEND_FOUND);
677          logger.debug(INFO_UPGRADE_REBUILD_INDEX_DECLINED, indexesToRebuild);
678          context.notifyProgress(pnc.setProgress(100));
679        }
680      }
681    };
682  }
683
684  /**
685   * Creates a file object representing config/upgrade/schema.ldif.current which
686   * the server creates the first time it starts if there are schema
687   * customizations.
688   *
689   * @return An upgrade task which upgrade the config/upgrade folder, creating a
690   *         new schema.ldif.rev which is needed after schema customization for
691   *         starting correctly the server.
692   */
693  public static UpgradeTask updateConfigUpgradeFolder()
694  {
695    return new AbstractUpgradeTask()
696    {
697      @Override
698      public void perform(final UpgradeContext context) throws ClientException
699      {
700        final LocalizableMessage msg = INFO_UPGRADE_TASK_REFRESH_UPGRADE_DIRECTORY.get();
701        logger.debug(msg);
702
703        final ProgressNotificationCallback pnc = new ProgressNotificationCallback(0, msg, 20);
704        context.notifyProgress(pnc);
705
706        try
707        {
708          String toRevision = String.valueOf(context.getToVersion().getRevisionNumber());
709          updateConfigUpgradeSchemaFile(configSchemaDirectory, toRevision);
710
711          context.notifyProgress(pnc.setProgress(100));
712        }
713        catch (final Exception ex)
714        {
715          manageTaskException(context, ERR_UPGRADE_CONFIG_ERROR_UPGRADE_FOLDER.get(ex.getMessage()), pnc);
716        }
717      }
718    };
719  }
720
721  /**
722   * Renames the SNMP security config file if it exists. Since 2.5.0.7466 this
723   * file has been renamed.
724   *
725   * @param summary
726   *          The summary of this upgrade task.
727   * @return An upgrade task which renames the old SNMP security config file if
728   *         it exists.
729   */
730  public static UpgradeTask renameSnmpSecurityConfig(final LocalizableMessage summary)
731  {
732    return new AbstractUpgradeTask()
733    {
734      @Override
735      public void perform(final UpgradeContext context) throws ClientException
736      {
737        /*
738         * Snmp config file contains old name in old version(like 2.4.5), in
739         * order to make sure the process will still work after upgrade, we need
740         * to rename it - only if it exists.
741         */
742        final File snmpDir = UpgradeUtils.configSnmpSecurityDirectory;
743        if (snmpDir.exists())
744        {
745          ProgressNotificationCallback pnc =
746              new ProgressNotificationCallback(0, summary, 0);
747          try
748          {
749            final File oldSnmpConfig = new File(snmpDir, "opends-snmp.security");
750            if (oldSnmpConfig.exists())
751            {
752              context.notifyProgress(pnc.setProgress(20));
753              logger.debug(summary);
754
755              final File snmpConfig = new File(snmpDir, "opendj-snmp.security");
756              FileManager.rename(oldSnmpConfig, snmpConfig);
757
758              context.notifyProgress(pnc.setProgress(100));
759            }
760          }
761          catch (final Exception ex)
762          {
763            LocalizableMessage msg = ERR_UPGRADE_RENAME_SNMP_SECURITY_CONFIG_FILE.get(ex.getMessage());
764            manageTaskException(context, msg, pnc);
765          }
766        }
767      }
768    };
769  }
770
771  /**
772   * Removes the specified file from the file-system.
773   *
774   * @param file
775   *          The file to be removed.
776   * @return An upgrade task which removes the specified file from the file-system.
777   */
778  public static UpgradeTask deleteFile(final File file)
779  {
780    return new AbstractUpgradeTask()
781    {
782      @Override
783      public void perform(UpgradeContext context) throws ClientException
784      {
785        LocalizableMessage msg = UPGRADE_TASK_DELETE_FILE.get(file);
786        ProgressNotificationCallback pnc = new ProgressNotificationCallback(0, msg, 0);
787        context.notifyProgress(pnc);
788        try
789        {
790          FileManager.deleteRecursively(file);
791          context.notifyProgress(pnc.setProgress(100));
792        }
793        catch (Exception e)
794        {
795          logger.error(LocalizableMessage.raw(e.getMessage()));
796          context.notifyProgress(pnc.setProgress(-1));
797        }
798      }
799    };
800  }
801
802  private static void displayChangeCount(final String fileName,
803      final int changeCount)
804  {
805    if (changeCount != 0)
806    {
807      logger.debug(INFO_UPGRADE_CHANGE_DONE_IN_SPECIFIC_FILE, fileName, changeCount);
808    }
809    else
810    {
811      logger.debug(INFO_UPGRADE_NO_CHANGE_DONE_IN_SPECIFIC_FILE, fileName);
812    }
813  }
814
815  private static void displayTaskLogInformation(final String summary,
816      final String filter, final String... ldif)
817  {
818    logger.debug(LocalizableMessage.raw(summary));
819    if (filter != null)
820    {
821      logger.debug(LocalizableMessage.raw(filter));
822    }
823    if (ldif != null)
824    {
825      logger.debug(LocalizableMessage.raw(Arrays.toString(ldif)));
826    }
827  }
828
829  private static void manageTaskException(final UpgradeContext context,
830      final LocalizableMessage message, final ProgressNotificationCallback pnc)
831      throws ClientException
832  {
833    countErrors++;
834    context.notifyProgress(pnc.setProgress(-100));
835    logger.error(message);
836    if (!context.isIgnoreErrorsMode())
837    {
838      throw new ClientException(ReturnCode.ERROR_UNEXPECTED, message);
839    }
840  }
841
842  private static UpgradeTask updateConfigEntry(final LocalizableMessage summary, final String filter,
843      final ChangeOperationType changeOperationType, final String... ldif)
844  {
845    return new AbstractUpgradeTask()
846    {
847      @Override
848      public void perform(final UpgradeContext context) throws ClientException
849      {
850        performConfigFileUpdate(summary, filter, changeOperationType, context, ldif);
851      }
852    };
853  }
854
855  private static void performConfigFileUpdate(final LocalizableMessage summary, final String filter,
856      final ChangeOperationType changeOperationType,
857      final UpgradeContext context, final String... ldif)
858      throws ClientException
859  {
860    displayTaskLogInformation(summary.toString(), filter, ldif);
861
862    final ProgressNotificationCallback pnc =
863        new ProgressNotificationCallback(0, summary, 20);
864
865    context.notifyProgress(pnc);
866
867    try
868    {
869      final File configFile =
870          new File(configDirectory, Installation.CURRENT_CONFIG_FILE_NAME);
871
872      final Filter filterVal = filter != null ? Filter.valueOf(filter) : null;
873      final int changeCount = updateConfigFile(
874          configFile.getPath(), filterVal, changeOperationType, ldif);
875
876      displayChangeCount(configFile.getPath(), changeCount);
877
878      context.notifyProgress(pnc.setProgress(100));
879    }
880    catch (final Exception e)
881    {
882      manageTaskException(context, LocalizableMessage.raw(e.getMessage()), pnc);
883    }
884  }
885
886  /** Prevent instantiation. */
887  private UpgradeTasks()
888  {
889    // Do nothing.
890  }
891}