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 * Copyright 2006-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2012-2016 ForgeRock AS.
016 */
017package org.opends.server.tools;
018
019import static org.opends.messages.ToolMessages.*;
020import static org.opends.server.config.ConfigConstants.*;
021import static org.opends.server.util.StaticUtils.*;
022import static com.forgerock.opendj.cli.ArgumentConstants.*;
023import static com.forgerock.opendj.cli.CommonArguments.*;
024import static com.forgerock.opendj.cli.Utils.*;
025
026import java.io.OutputStream;
027import java.io.PrintStream;
028import java.util.ArrayList;
029import java.util.HashSet;
030import java.util.List;
031import java.util.Set;
032
033import org.forgerock.i18n.slf4j.LocalizedLogger;
034import org.forgerock.opendj.ldap.DN;
035import org.forgerock.opendj.ldap.schema.AttributeType;
036import org.forgerock.opendj.server.config.server.BackendCfg;
037import org.opends.server.api.Backend;
038import org.opends.server.api.Backend.BackendOperation;
039import org.opends.server.api.plugin.PluginType;
040import org.opends.server.core.DirectoryServer;
041import org.opends.server.core.LockFileManager;
042import org.opends.server.loggers.JDKLogging;
043import org.opends.server.protocols.ldap.LDAPAttribute;
044import org.opends.server.tasks.ExportTask;
045import org.opends.server.tools.tasks.TaskTool;
046import org.opends.server.types.DirectoryException;
047import org.opends.server.types.ExistingFileBehavior;
048import org.opends.server.types.InitializationException;
049import org.opends.server.types.LDIFExportConfig;
050import org.opends.server.types.NullOutputStream;
051import org.opends.server.types.RawAttribute;
052import org.opends.server.types.SearchFilter;
053import org.opends.server.util.cli.LDAPConnectionArgumentParser;
054
055import com.forgerock.opendj.cli.Argument;
056import com.forgerock.opendj.cli.ArgumentException;
057import com.forgerock.opendj.cli.BooleanArgument;
058import com.forgerock.opendj.cli.ClientException;
059import com.forgerock.opendj.cli.IntegerArgument;
060import com.forgerock.opendj.cli.StringArgument;
061
062/**
063 * This program provides a utility that may be used to export the contents of a
064 * Directory Server backend to an LDIF file.  Depending on the arguments given,
065 * this program will either perform the export directly as a process that
066 * runs separate from Directory Server; or by scheduling a task to perform the
067 * action within the Directory Server via the tasks interface.
068 */
069public class ExportLDIF extends TaskTool {
070
071  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
072
073  /**
074   * The main method for ExportLDIF tool.
075   *
076   * @param  args  The command-line arguments provided to this program.
077   */
078  public static void main(String[] args)
079  {
080    int retCode = mainExportLDIF(args, true, System.out, System.err);
081    if(retCode != 0)
082    {
083      System.exit(filterExitCode(retCode));
084    }
085  }
086
087  /**
088   * Processes the command-line arguments and invokes the export process.
089   *
090   * @param  args              The command-line arguments provided to this
091   *                           program.
092   * @param  initializeServer  Indicates whether to initialize the server.
093   * @param  outStream         The output stream to use for standard output, or
094   *                           {@code null} if standard output is not needed.
095   * @param  errStream         The output stream to use for standard error, or
096   *                           {@code null} if standard error is not needed.
097   *
098   * @return The error code.
099   */
100  public static int mainExportLDIF(String[] args, boolean initializeServer,
101                                   OutputStream outStream,
102                                   OutputStream errStream)
103  {
104    ExportLDIF tool = new ExportLDIF();
105    return tool.process(args, initializeServer, outStream, errStream);
106  }
107
108  /** Define the command-line arguments that may be used with this program. */
109  private BooleanArgument appendToLDIF;
110  private BooleanArgument compressLDIF;
111  private BooleanArgument displayUsage;
112  private BooleanArgument encryptLDIF;
113  private BooleanArgument excludeOperationalAttrs;
114  private BooleanArgument signHash;
115  private IntegerArgument wrapColumn;
116  private StringArgument  backendID;
117  private StringArgument  configFile;
118  private StringArgument  excludeAttributeStrings;
119  private StringArgument  excludeBranchStrings;
120  private StringArgument  excludeFilterStrings;
121  private StringArgument  includeAttributeStrings;
122  private StringArgument  includeBranchStrings;
123  private StringArgument  includeFilterStrings;
124  private StringArgument  ldifFile;
125
126  private int process(String[] args, boolean initializeServer,
127                      OutputStream outStream, OutputStream errStream) {
128
129    PrintStream out = NullOutputStream.wrapOrNullStream(outStream);
130    PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
131    JDKLogging.disableLogging();
132
133    // Create the command-line argument parser for use with this program.
134    LDAPConnectionArgumentParser argParser =
135            createArgParser("org.opends.server.tools.ExportLDIF",
136                            INFO_LDIFEXPORT_TOOL_DESCRIPTION.get());
137    argParser.setShortToolDescription(REF_SHORT_DESC_EXPORT_LDIF.get());
138
139
140    // Initialize all the command-line argument types and register them with the parser.
141    try
142    {
143      configFile =
144              StringArgument.builder("configFile")
145                      .shortIdentifier('f')
146                      .description(INFO_DESCRIPTION_CONFIG_FILE.get())
147                      .hidden()
148                      .required()
149                      .valuePlaceholder(INFO_CONFIGFILE_PLACEHOLDER.get())
150                      .buildAndAddToParser(argParser);
151      ldifFile =
152              StringArgument.builder(OPTION_LONG_LDIF_FILE)
153                      .shortIdentifier(OPTION_SHORT_LDIF_FILE)
154                      .description(INFO_LDIFEXPORT_DESCRIPTION_LDIF_FILE.get())
155                      .required()
156                      .valuePlaceholder(INFO_LDIFFILE_PLACEHOLDER.get())
157                      .buildAndAddToParser(argParser);
158      appendToLDIF =
159              BooleanArgument.builder("appendToLDIF")
160                      .shortIdentifier('a')
161                      .description(INFO_LDIFEXPORT_DESCRIPTION_APPEND_TO_LDIF.get())
162                      .buildAndAddToParser(argParser);
163      backendID =
164              StringArgument.builder("backendID")
165                      .shortIdentifier('n')
166                      .description(INFO_LDIFEXPORT_DESCRIPTION_BACKEND_ID.get())
167                      .required()
168                      .valuePlaceholder(INFO_BACKENDNAME_PLACEHOLDER.get())
169                      .buildAndAddToParser(argParser);
170      includeBranchStrings =
171              StringArgument.builder("includeBranch")
172                      .shortIdentifier('b')
173                      .description(INFO_LDIFEXPORT_DESCRIPTION_INCLUDE_BRANCH.get())
174                      .multiValued()
175                      .valuePlaceholder(INFO_BRANCH_DN_PLACEHOLDER.get())
176                      .buildAndAddToParser(argParser);
177      excludeBranchStrings =
178              StringArgument.builder("excludeBranch")
179                      .shortIdentifier('B')
180                      .description(INFO_LDIFEXPORT_DESCRIPTION_EXCLUDE_BRANCH.get())
181                      .multiValued()
182                      .valuePlaceholder(INFO_BRANCH_DN_PLACEHOLDER.get())
183                      .buildAndAddToParser(argParser);
184      includeAttributeStrings =
185              StringArgument.builder("includeAttribute")
186                      .shortIdentifier('i')
187                      .description(INFO_LDIFEXPORT_DESCRIPTION_INCLUDE_ATTRIBUTE.get())
188                      .multiValued()
189                      .valuePlaceholder(INFO_ATTRIBUTE_PLACEHOLDER.get())
190                      .buildAndAddToParser(argParser);
191      excludeAttributeStrings =
192              StringArgument.builder("excludeAttribute")
193                      .shortIdentifier('e')
194                      .description(INFO_LDIFEXPORT_DESCRIPTION_EXCLUDE_ATTRIBUTE.get())
195                      .multiValued()
196                      .valuePlaceholder(INFO_ATTRIBUTE_PLACEHOLDER.get())
197                      .buildAndAddToParser(argParser);
198      includeFilterStrings =
199              StringArgument.builder("includeFilter")
200                      .shortIdentifier('I')
201                      .description(INFO_LDIFEXPORT_DESCRIPTION_INCLUDE_FILTER.get())
202                      .multiValued()
203                      .valuePlaceholder(INFO_FILTER_PLACEHOLDER.get())
204                      .buildAndAddToParser(argParser);
205      excludeFilterStrings =
206              StringArgument.builder("excludeFilter")
207                      .shortIdentifier('E')
208                      .description(INFO_LDIFEXPORT_DESCRIPTION_EXCLUDE_FILTER.get())
209                      .multiValued()
210                      .valuePlaceholder(INFO_FILTER_PLACEHOLDER.get())
211                      .buildAndAddToParser(argParser);
212      excludeOperationalAttrs =
213              BooleanArgument.builder("excludeOperational")
214                      .shortIdentifier('O')
215                      .description(INFO_LDIFEXPORT_DESCRIPTION_EXCLUDE_OPERATIONAL.get())
216                      .buildAndAddToParser(argParser);
217      wrapColumn =
218              IntegerArgument.builder("wrapColumn")
219                      .description(INFO_LDIFEXPORT_DESCRIPTION_WRAP_COLUMN.get())
220                      .lowerBound(0)
221                      .defaultValue(0)
222                      .valuePlaceholder(INFO_WRAP_COLUMN_PLACEHOLDER.get())
223                      .buildAndAddToParser(argParser);
224      compressLDIF =
225              BooleanArgument.builder(OPTION_LONG_COMPRESS)
226                      .shortIdentifier(OPTION_SHORT_COMPRESS)
227                      .description(INFO_LDIFEXPORT_DESCRIPTION_COMPRESS_LDIF.get())
228                      .buildAndAddToParser(argParser);
229      encryptLDIF =
230              BooleanArgument.builder("encryptLDIF")
231                      .shortIdentifier('y')
232                      .description(INFO_LDIFEXPORT_DESCRIPTION_ENCRYPT_LDIF.get())
233                      .hidden() // See issue OPENDJ-448
234                      .buildAndAddToParser(argParser);
235      signHash =
236              BooleanArgument.builder("signHash")
237                      .shortIdentifier('s')
238                      .description(INFO_LDIFEXPORT_DESCRIPTION_SIGN_HASH.get())
239                      .hidden() // See issue OPENDJ-448
240                      .buildAndAddToParser(argParser);
241
242      displayUsage = showUsageArgument();
243      argParser.addArgument(displayUsage);
244      argParser.setUsageArgument(displayUsage);
245    }
246    catch (ArgumentException ae)
247    {
248      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
249      return 1;
250    }
251
252
253    // Init the default values so that they can appear also on the usage.
254    argParser.getArguments().initArgumentsWithConfiguration(argParser);
255
256    // Parse the command-line arguments provided to this program.
257    try
258    {
259      argParser.parseArguments(args);
260      validateTaskArgs();
261    }
262    catch (ArgumentException ae)
263    {
264      argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
265      return 1;
266    }
267    catch (ClientException ce)
268    {
269      // No need to display the usage since the problem comes with a provided value.
270      printWrappedText(err, ce.getMessageObject());
271      return 1;
272    }
273
274
275    // If we should just display usage or version information,
276    // then print it and exit.
277    if (argParser.usageOrVersionDisplayed())
278    {
279      return 0;
280    }
281
282    // Checks the version - if upgrade required, the tool is unusable
283    try
284    {
285      checkVersion();
286    }
287    catch (InitializationException e)
288    {
289      printWrappedText(err, e.getMessage());
290      return 1;
291    }
292
293    return process(argParser, initializeServer, out, err);
294  }
295
296  @Override
297  public void addTaskAttributes(List<RawAttribute> attributes)
298  {
299    // Required attributes
300    attributes.add(new LDAPAttribute(ATTR_TASK_EXPORT_LDIF_FILE, ldifFile.getValue()));
301    attributes.add(new LDAPAttribute(ATTR_TASK_EXPORT_BACKEND_ID, backendID.getValue()));
302
303    // Optional attributes
304    addAttribute(attributes, ATTR_TASK_EXPORT_APPEND_TO_LDIF, appendToLDIF);
305    addAttribute(attributes, ATTR_TASK_EXPORT_COMPRESS_LDIF, compressLDIF);
306    addAttribute(attributes, ATTR_TASK_EXPORT_ENCRYPT_LDIF, encryptLDIF);
307    addAttribute(attributes, ATTR_TASK_EXPORT_SIGN_HASH, signHash);
308    addAttribute(attributes, ATTR_TASK_EXPORT_INCLUDE_ATTRIBUTE, includeAttributeStrings.getValues());
309    addAttribute(attributes, ATTR_TASK_EXPORT_EXCLUDE_ATTRIBUTE, excludeAttributeStrings.getValues());
310    addAttribute(attributes, ATTR_TASK_EXPORT_INCLUDE_FILTER, includeFilterStrings.getValues());
311    addAttribute(attributes, ATTR_TASK_EXPORT_EXCLUDE_FILTER, excludeFilterStrings.getValues());
312    addAttribute(attributes, ATTR_TASK_EXPORT_INCLUDE_BRANCH, includeBranchStrings.getValues());
313    addAttribute(attributes, ATTR_TASK_EXPORT_EXCLUDE_BRANCH, excludeBranchStrings.getValues());
314    addAttribute(attributes, ATTR_TASK_EXPORT_WRAP_COLUMN, wrapColumn);
315
316    if (excludeOperationalAttrs.isPresent())
317    {
318      attributes.add(
319          new LDAPAttribute(ATTR_TASK_EXPORT_INCLUDE_OPERATIONAL_ATTRIBUTES, "false"));
320    }
321  }
322
323  private void addAttribute(List<RawAttribute> attributes, String attrName, Argument arg)
324  {
325    if (arg.getValue() != null && !arg.getValue().equals(arg.getDefaultValue()))
326    {
327      attributes.add(new LDAPAttribute(attrName, arg.getValue()));
328    }
329  }
330
331  private void addAttribute(List<RawAttribute> attributes, String attrName, List<String> attrValues)
332  {
333    if (attrValues != null && !attrValues.isEmpty())
334    {
335      attributes.add(new LDAPAttribute(attrName, attrValues));
336    }
337  }
338
339  @Override
340  public String getTaskObjectclass() {
341    return "ds-task-export";
342  }
343
344  @Override
345  public Class<?> getTaskClass() {
346    return ExportTask.class;
347  }
348
349  @Override
350  protected int processLocal(boolean initializeServer, PrintStream out, PrintStream err)
351  {
352    if (initializeServer)
353    {
354      try
355      {
356        new DirectoryServer.InitializationBuilder(configFile.getValue())
357            .requireCryptoServices()
358            .requireUserPlugins(PluginType.LDIF_EXPORT)
359            .requireErrorAndDebugLogPublisher(out, err)
360            .initialize();
361      }
362      catch (InitializationException ie)
363      {
364        printWrappedText(err, ERR_CANNOT_INITIALIZE_SERVER_COMPONENTS.get(getExceptionMessage(ie)));
365        return 1;
366      }
367    }
368
369    // See if there were any user-defined sets of include/exclude attributes or
370    // filters.  If so, then process them.
371    Set<AttributeType> excludeAttributes = toAttributeTypes(excludeAttributeStrings);
372    Set<AttributeType> includeAttributes = toAttributeTypes(includeAttributeStrings);
373
374    ArrayList<SearchFilter> excludeFilters;
375    if (excludeFilterStrings == null)
376    {
377      excludeFilters = null;
378    }
379    else
380    {
381      excludeFilters = new ArrayList<>();
382      for (String filterString : excludeFilterStrings.getValues())
383      {
384        try
385        {
386          excludeFilters.add(SearchFilter.createFilterFromString(filterString));
387        }
388        catch (DirectoryException de)
389        {
390          logger.error(ERR_LDIFEXPORT_CANNOT_PARSE_EXCLUDE_FILTER, filterString, de.getMessageObject());
391          return 1;
392        }
393        catch (Exception e)
394        {
395          logger.error(ERR_LDIFEXPORT_CANNOT_PARSE_EXCLUDE_FILTER, filterString, getExceptionMessage(e));
396          return 1;
397        }
398      }
399    }
400
401    ArrayList<SearchFilter> includeFilters;
402    if (includeFilterStrings == null)
403    {
404      includeFilters = null;
405    }
406    else
407    {
408      includeFilters = new ArrayList<>();
409      for (String filterString : includeFilterStrings.getValues())
410      {
411        try
412        {
413          includeFilters.add(SearchFilter.createFilterFromString(filterString));
414        }
415        catch (DirectoryException de)
416        {
417          logger.error(ERR_LDIFEXPORT_CANNOT_PARSE_INCLUDE_FILTER, filterString, de.getMessageObject());
418          return 1;
419        }
420        catch (Exception e)
421        {
422          logger.error(ERR_LDIFEXPORT_CANNOT_PARSE_INCLUDE_FILTER, filterString, getExceptionMessage(e));
423          return 1;
424        }
425      }
426    }
427
428
429    // Get information about the backends defined in the server.  Iterate
430    // through them, finding the one backend that should be used for the export,
431    // and also finding backends with subordinate base DNs that should be
432    // excluded from the export.
433    Backend<?>    backend                = null;
434    List<DN>      baseDNList             = null;
435    List<DN>      defaultIncludeBranches = null;
436    ArrayList<DN> excludeBranches        = null;
437
438    List<Backend<?>> backendList = new ArrayList<>();
439    List<BackendCfg> entryList = new ArrayList<>();
440    List<List<DN>> dnList = new ArrayList<>();
441    BackendToolUtils.getBackends(backendList, entryList, dnList);
442
443    int numBackends = backendList.size();
444    for (int i=0; i < numBackends; i++)
445    {
446      Backend<?> b = backendList.get(i);
447      if (! backendID.getValue().equals(b.getBackendID()))
448      {
449        continue;
450      }
451
452      if (backend == null)
453      {
454        backend                = b;
455        baseDNList             = dnList.get(i);
456        defaultIncludeBranches = dnList.get(i);
457      }
458      else
459      {
460        logger.error(ERR_LDIFEXPORT_MULTIPLE_BACKENDS_FOR_ID, backendID.getValue());
461        return 1;
462      }
463    }
464
465    if (backend == null)
466    {
467      logger.error(ERR_LDIFEXPORT_NO_BACKENDS_FOR_ID, backendID.getValue());
468      return 1;
469    }
470    else if (!backend.supports(BackendOperation.RESTORE))
471    {
472      logger.error(ERR_LDIFEXPORT_CANNOT_EXPORT_BACKEND, backendID.getValue());
473      return 1;
474    }
475
476    if (excludeBranchStrings.isPresent())
477    {
478      excludeBranches = new ArrayList<>();
479      for (String s : excludeBranchStrings.getValues())
480      {
481        DN excludeBranch;
482        try
483        {
484          excludeBranch = DN.valueOf(s);
485        }
486        catch (Exception e)
487        {
488          logger.error(ERR_LDIFEXPORT_CANNOT_DECODE_EXCLUDE_BASE, s, getExceptionMessage(e));
489          return 1;
490        }
491
492        if (! excludeBranches.contains(excludeBranch))
493        {
494          excludeBranches.add(excludeBranch);
495        }
496      }
497    }
498
499
500    List<DN> includeBranches;
501    if (includeBranchStrings.isPresent())
502    {
503      includeBranches = new ArrayList<>();
504      for (String s : includeBranchStrings.getValues())
505      {
506        DN includeBranch;
507        try
508        {
509          includeBranch = DN.valueOf(s);
510        }
511        catch (Exception e)
512        {
513          logger.error(ERR_LDIFIMPORT_CANNOT_DECODE_INCLUDE_BASE, s, getExceptionMessage(e));
514          return 1;
515        }
516
517        if (! Backend.handlesEntry(includeBranch, defaultIncludeBranches,
518                                   excludeBranches))
519        {
520          logger.error(ERR_LDIFEXPORT_INVALID_INCLUDE_BASE, s, backendID.getValue());
521          return 1;
522        }
523
524        includeBranches.add(includeBranch);
525      }
526    }
527    else
528    {
529      includeBranches = defaultIncludeBranches;
530    }
531
532
533    // Create the LDIF export configuration to use when reading the LDIF.
534    ExistingFileBehavior existingBehavior;
535    if (appendToLDIF.isPresent())
536    {
537      existingBehavior = ExistingFileBehavior.APPEND;
538    }
539    else
540    {
541      existingBehavior = ExistingFileBehavior.OVERWRITE;
542    }
543
544    LDIFExportConfig exportConfig = new LDIFExportConfig(ldifFile.getValue(),
545                                                         existingBehavior);
546    exportConfig.setCompressData(compressLDIF.isPresent());
547    exportConfig.setEncryptData(encryptLDIF.isPresent());
548    exportConfig.setExcludeAttributes(excludeAttributes);
549    exportConfig.setExcludeBranches(excludeBranches);
550    exportConfig.setExcludeFilters(excludeFilters);
551    exportConfig.setIncludeAttributes(includeAttributes);
552    exportConfig.setIncludeBranches(includeBranches);
553    exportConfig.setIncludeFilters(includeFilters);
554    exportConfig.setSignHash(signHash.isPresent());
555    exportConfig.setIncludeOperationalAttributes(
556                      !excludeOperationalAttrs.isPresent());
557
558    // FIXME -- Should this be conditional?
559    exportConfig.setInvokeExportPlugins(true);
560
561    try
562    {
563      exportConfig.setWrapColumn(wrapColumn.getIntValue());
564    }
565    catch (ArgumentException ae)
566    {
567      logger.error(ERR_LDIFEXPORT_CANNOT_DECODE_WRAP_COLUMN_AS_INTEGER, wrapColumn.getValue());
568      return 1;
569    }
570
571
572    // Get the set of base DNs for the backend as an array.
573    DN[] baseDNs = new DN[baseDNList.size()];
574    baseDNList.toArray(baseDNs);
575
576
577    // Acquire a shared lock for the backend.
578    try
579    {
580      String lockFile = LockFileManager.getBackendLockFileName(backend);
581      StringBuilder failureReason = new StringBuilder();
582      if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
583      {
584        logger.error(ERR_LDIFEXPORT_CANNOT_LOCK_BACKEND, backend.getBackendID(), failureReason);
585        return 1;
586      }
587    }
588    catch (Exception e)
589    {
590      logger.error(ERR_LDIFEXPORT_CANNOT_LOCK_BACKEND, backend.getBackendID(), getExceptionMessage(e));
591      return 1;
592    }
593
594    boolean errorOccurred = false;
595
596    // Launch the export.
597    try
598    {
599      backend.exportLDIF(exportConfig);
600    }
601    catch (DirectoryException de)
602    {
603      logger.error(ERR_LDIFEXPORT_ERROR_DURING_EXPORT, de.getMessageObject());
604      errorOccurred = true;
605    }
606    catch (Exception e)
607    {
608      logger.error(ERR_LDIFEXPORT_ERROR_DURING_EXPORT, getExceptionMessage(e));
609      errorOccurred = true;
610    }
611
612
613    // Release the shared lock on the backend.
614    try
615    {
616      String lockFile = LockFileManager.getBackendLockFileName(backend);
617      StringBuilder failureReason = new StringBuilder();
618      if (! LockFileManager.releaseLock(lockFile, failureReason))
619      {
620        logger.warn(WARN_LDIFEXPORT_CANNOT_UNLOCK_BACKEND, backend.getBackendID(), failureReason);
621      }
622    }
623    catch (Exception e)
624    {
625      logger.warn(WARN_LDIFEXPORT_CANNOT_UNLOCK_BACKEND, backend.getBackendID(), getExceptionMessage(e));
626    }
627
628
629    // Clean up after the export by closing the export config.
630    exportConfig.close();
631    return !errorOccurred ? 0 : 1;
632  }
633
634  @Override
635  protected void cleanup()
636  {
637    DirectoryServer.shutdownBackends();
638  }
639
640  private Set<AttributeType> toAttributeTypes(StringArgument attributeArg)
641  {
642    if (attributeArg == null)
643    {
644      return null;
645    }
646
647    Set<AttributeType> results = new HashSet<>();
648    for (String attrName : attributeArg.getValues())
649    {
650      results.add(DirectoryServer.getSchema().getAttributeType(attrName));
651    }
652    return results;
653  }
654
655  @Override
656  public String getTaskId() {
657    // NYI.
658    return null;
659  }
660}