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 *      Copyright 2006-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2015 ForgeRock AS.
026 */
027package org.opends.server.backends.jeb;
028
029import static org.opends.messages.ToolMessages.*;
030import static org.opends.server.util.StaticUtils.*;
031
032import static com.forgerock.opendj.cli.ArgumentConstants.*;
033import static com.forgerock.opendj.cli.Utils.*;
034
035import java.io.OutputStream;
036import java.io.PrintStream;
037import java.text.ParseException;
038import java.util.ArrayList;
039import java.util.Collection;
040import java.util.Comparator;
041import java.util.HashMap;
042import java.util.LinkedHashMap;
043import java.util.List;
044import java.util.Map;
045
046import org.forgerock.i18n.LocalizableMessage;
047import org.forgerock.opendj.config.server.ConfigException;
048import org.forgerock.opendj.ldap.ByteSequence;
049import org.forgerock.opendj.ldap.ByteString;
050import org.forgerock.opendj.ldap.ByteStringBuilder;
051import org.opends.server.admin.std.server.BackendCfg;
052import org.opends.server.admin.std.server.LocalDBBackendCfg;
053import org.opends.server.api.Backend;
054import org.opends.server.core.CoreConfigManager;
055import org.opends.server.core.DirectoryServer;
056import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler;
057import org.opends.server.core.LockFileManager;
058import org.opends.server.extensions.ConfigFileHandler;
059import org.opends.server.loggers.JDKLogging;
060import org.opends.server.tools.BackendToolUtils;
061import org.opends.server.types.DN;
062import org.opends.server.types.DirectoryException;
063import org.opends.server.types.InitializationException;
064import org.opends.server.types.NullOutputStream;
065import org.opends.server.types.SortKey;
066import org.opends.server.util.BuildVersion;
067import org.opends.server.util.StaticUtils;
068
069import com.forgerock.opendj.cli.Argument;
070import com.forgerock.opendj.cli.ArgumentException;
071import com.forgerock.opendj.cli.BooleanArgument;
072import com.forgerock.opendj.cli.CommonArguments;
073import com.forgerock.opendj.cli.IntegerArgument;
074import com.forgerock.opendj.cli.StringArgument;
075import com.forgerock.opendj.cli.SubCommand;
076import com.forgerock.opendj.cli.SubCommandArgumentParser;
077import com.forgerock.opendj.cli.TableBuilder;
078import com.forgerock.opendj.cli.TextTablePrinter;
079import com.sleepycat.je.Cursor;
080import com.sleepycat.je.CursorConfig;
081import com.sleepycat.je.DatabaseEntry;
082import com.sleepycat.je.DatabaseException;
083import com.sleepycat.je.LockMode;
084import com.sleepycat.je.OperationStatus;
085
086/**
087 * This program provides a utility that may be used to debug a JE backend. This
088 * tool provides the ability list various containers in the backend as well as
089 * dump the contents of database containers. This will be
090 * a process that is intended to run separate from Directory Server and not
091 * internally within the server process (e.g., via the tasks interface).
092 */
093public class DBTest
094{
095  /** The error stream which this application should use. */
096  private final PrintStream err;
097
098  /** The output stream which this application should use. */
099  private final PrintStream out;
100
101  /**
102   * Flag indicating whether or not the global arguments have already been
103   * initialized.
104   */
105  private boolean globalArgumentsInitialized;
106
107  /** The command-line argument parser. */
108  private final SubCommandArgumentParser parser;
109
110  /** The argument which should be used to request usage information. */
111  private BooleanArgument showUsageArgument;
112
113  /** The argument which should be used to specify the config class. */
114  private StringArgument configClass;
115
116  /** THe argument which should be used to specify the config file. */
117  private StringArgument configFile;
118
119  /**
120   * Flag indicating whether or not the sub-commands have already been
121   * initialized.
122   */
123  private boolean subCommandsInitialized;
124
125
126
127  /**
128   * Provides the command-line arguments to the main application for
129   * processing.
130   *
131   * @param args
132   *          The set of command-line arguments provided to this
133   *          program.
134   */
135  public static void main(String[] args) {
136    int exitCode = main(args, true, System.out, System.err);
137    if (exitCode != 0) {
138      System.exit(filterExitCode(exitCode));
139    }
140  }
141
142
143  /**
144   * Provides the command-line arguments to the main application for
145   * processing and returns the exit code as an integer.
146   *
147   * @param args
148   *          The set of command-line arguments provided to this
149   *          program.
150   * @param initializeServer
151   *          Indicates whether to perform basic initialization (which
152   *          should not be done if the tool is running in the same
153   *          JVM as the server).
154   * @param outStream
155   *          The output stream for standard output.
156   * @param errStream
157   *          The output stream for standard error.
158   * @return Zero to indicate that the program completed successfully,
159   *         or non-zero to indicate that an error occurred.
160   */
161  public static int main(String[] args, boolean initializeServer,
162                         OutputStream outStream, OutputStream errStream) {
163    DBTest app = new DBTest(outStream, errStream);
164
165    // Run the application.
166    return app.run(args, initializeServer);
167  }
168
169  /**
170   * Creates a new dsconfig application instance.
171   *
172   * @param out
173   *          The application output stream.
174   * @param err
175   *          The application error stream.
176   */
177  public DBTest(OutputStream out, OutputStream err)
178  {
179    this.out = NullOutputStream.wrapOrNullStream(out);
180    this.err = NullOutputStream.wrapOrNullStream(err);
181    JDKLogging.disableLogging();
182
183    LocalizableMessage toolDescription = INFO_DESCRIPTION_DBTEST_TOOL.get();
184    this.parser = new SubCommandArgumentParser(getClass().getName(), toolDescription, false);
185    this.parser.setShortToolDescription(REF_SHORT_DESC_DBTEST.get());
186    this.parser.setVersionHandler(new DirectoryServerVersionHandler());
187  }
188
189  /**
190   * Registers the global arguments with the argument parser.
191   *
192   * @throws ArgumentException
193   *           If a global argument could not be registered.
194   */
195  private void initializeGlobalArguments() throws ArgumentException {
196    if (!globalArgumentsInitialized) {
197      configClass =
198          new StringArgument("configclass", OPTION_SHORT_CONFIG_CLASS,
199                             OPTION_LONG_CONFIG_CLASS, true, false,
200                             true, INFO_CONFIGCLASS_PLACEHOLDER.get(),
201                             ConfigFileHandler.class.getName(), null,
202                             INFO_DESCRIPTION_CONFIG_CLASS.get());
203      configClass.setHidden(true);
204
205      configFile =
206          new StringArgument("configfile", 'f', "configFile", true, false,
207                             true, INFO_CONFIGFILE_PLACEHOLDER.get(), null,
208                             null,
209                             INFO_DESCRIPTION_CONFIG_FILE.get());
210      configFile.setHidden(true);
211
212
213      showUsageArgument = CommonArguments.getShowUsage();
214
215      // Register the global arguments.
216      parser.addGlobalArgument(showUsageArgument);
217      parser.setUsageArgument(showUsageArgument, out);
218      parser.addGlobalArgument(configClass);
219      parser.addGlobalArgument(configFile);
220
221      globalArgumentsInitialized = true;
222    }
223  }
224
225
226
227  /**
228   * Registers the sub-commands with the argument parser.
229   *
230   * @throws ArgumentException
231   *           If a sub-command could not be created.
232   */
233  private void initializeSubCommands() throws ArgumentException {
234    if (!subCommandsInitialized) {
235      StringArgument backendID;
236      StringArgument baseDN;
237      StringArgument databaseName;
238      BooleanArgument skipDecode;
239      BooleanArgument statsOnly;
240      StringArgument maxKeyValue;
241      StringArgument minKeyValue;
242      IntegerArgument maxDataSize;
243      IntegerArgument minDataSize;
244      SubCommand sub;
245
246      sub = new SubCommand(parser, "list-root-containers",
247                     INFO_DESCRIPTION_DBTEST_SUBCMD_LIST_ROOT_CONTAINERS.get());
248
249
250      sub = new SubCommand(parser, "list-entry-containers",
251                    INFO_DESCRIPTION_DBTEST_SUBCMD_LIST_ENTRY_CONTAINERS.get());
252      backendID =
253          new StringArgument("backendid", 'n', "backendID", true, false, true,
254                             INFO_BACKENDNAME_PLACEHOLDER.get(), null, null,
255                             INFO_DESCRIPTION_DBTEST_BACKEND_ID.get());
256      sub.addArgument(backendID);
257
258
259      sub = new SubCommand(parser, "list-database-containers",
260                 INFO_DESCRIPTION_DBTEST_SUBCMD_LIST_DATABASE_CONTAINERS.get());
261      backendID =
262          new StringArgument("backendid", 'n', "backendID", true, false, true,
263                             INFO_BACKENDNAME_PLACEHOLDER.get(), null, null,
264                             INFO_DESCRIPTION_DBTEST_BACKEND_ID.get());
265      sub.addArgument(backendID);
266      baseDN =
267          new StringArgument("basedn", 'b', "baseDN", false,
268                             false, true, INFO_BASEDN_PLACEHOLDER.get(), null,
269                             null,
270                             INFO_DESCRIPTION_DBTEST_BASE_DN.get());
271      sub.addArgument(baseDN);
272
273
274      sub = new SubCommand(parser, "dump-database-container",
275                  INFO_DESCRIPTION_DBTEST_SUBCMD_DUMP_DATABASE_CONTAINER.get());
276      backendID =
277          new StringArgument("backendid", 'n', "backendID", true, false, true,
278                             INFO_BACKENDNAME_PLACEHOLDER.get(), null, null,
279                             INFO_DESCRIPTION_DBTEST_BACKEND_ID.get());
280      sub.addArgument(backendID);
281      baseDN =
282          new StringArgument("basedn", 'b', "baseDN", true,
283                             false, true, INFO_BASEDN_PLACEHOLDER.get(), null,
284                             null,
285                             INFO_DESCRIPTION_DBTEST_BASE_DN.get());
286      sub.addArgument(baseDN);
287      databaseName =
288          new StringArgument("databasename", 'd', "databaseName", true,
289                             false, true, INFO_DATABASE_NAME_PLACEHOLDER.get(),
290                             null, null,
291                             INFO_DESCRIPTION_DBTEST_DATABASE_NAME.get());
292      sub.addArgument(databaseName);
293      skipDecode =
294          new BooleanArgument("skipdecode", 'p', "skipDecode",
295                              INFO_DESCRIPTION_DBTEST_SKIP_DECODE.get());
296      sub.addArgument(skipDecode);
297      statsOnly =
298          new BooleanArgument("statsonly", 'q', "statsOnly",
299                              INFO_DESCRIPTION_DBTEST_STATS_ONLY.get());
300      sub.addArgument(statsOnly);
301      maxKeyValue = new StringArgument("maxkeyvalue", 'K', "maxKeyValue", false,
302                                       false, true,
303                                       INFO_MAX_KEY_VALUE_PLACEHOLDER.get(),
304                                       null, null,
305                                   INFO_DESCRIPTION_DBTEST_MAX_KEY_VALUE.get());
306      sub.addArgument(maxKeyValue);
307      minKeyValue = new StringArgument("minkeyvalue", 'k', "minKeyValue", false,
308                                       false, true,
309                                       INFO_MIN_KEY_VALUE_PLACEHOLDER.get(),
310                                       null,
311                                       null,
312                                   INFO_DESCRIPTION_DBTEST_MIN_KEY_VALUE.get());
313      sub.addArgument(minKeyValue);
314      maxDataSize = new IntegerArgument("maxdatasize", 'S', "maxDataSize",
315                                        false, false, true,
316                                        INFO_MAX_DATA_SIZE_PLACEHOLDER.get(),
317                                        -1,
318                                        null,
319                                   INFO_DESCRIPTION_DBTEST_MAX_DATA_SIZE.get());
320      sub.addArgument(maxDataSize);
321      minDataSize = new IntegerArgument("mindatasize", 's', "minDataSize",
322                                        false, false, true,
323                                        INFO_MIN_DATA_SIZE_PLACEHOLDER.get(),
324                                        -1,
325                                        null,
326                                   INFO_DESCRIPTION_DBTEST_MIN_DATA_SIZE.get());
327      sub.addArgument(minDataSize);
328
329
330      sub = new SubCommand(parser, "list-index-status",
331                        INFO_DESCRIPTION_DBTEST_SUBCMD_LIST_INDEX_STATUS.get());
332      sub.setDocDescriptionSupplement(
333              SUPPLEMENT_DESCRIPTION_DBTEST_SUBCMD_LIST_INDEX_STATUS.get());
334      backendID =
335          new StringArgument("backendid", 'n', "backendID", true, false, true,
336                             INFO_BACKENDNAME_PLACEHOLDER.get(), null, null,
337                             INFO_DESCRIPTION_DBTEST_BACKEND_ID.get());
338      sub.addArgument(backendID);
339      baseDN =
340          new StringArgument("basedn", 'b', "baseDN", true,
341                             true, true, INFO_BASEDN_PLACEHOLDER.get(), null,
342                             null,
343                             INFO_DESCRIPTION_DBTEST_BASE_DN.get());
344      sub.addArgument(baseDN);
345
346      subCommandsInitialized = true;
347    }
348  }
349
350
351  /**
352   * Parses the provided command-line arguments and makes the
353   * appropriate changes to the Directory Server configuration.
354   *
355   * @param args
356   *          The command-line arguments provided to this program.
357   * @param initializeServer
358   *          Indicates whether to perform basic initialization (which
359   *          should not be done if the tool is running in the same
360   *          JVM as the server).
361   * @return The exit code from the configuration processing. A
362   *         nonzero value indicates that there was some kind of
363   *         problem during the configuration processing.
364   */
365  private int run(String[] args, boolean initializeServer) {
366
367    // Register global arguments and sub-commands.
368    try {
369      initializeGlobalArguments();
370      initializeSubCommands();
371    } catch (ArgumentException e) {
372      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(e.getMessage()));
373      return 1;
374    }
375
376    // Parse the command-line arguments provided to this program.
377    try {
378      parser.parseArguments(args);
379    } catch (ArgumentException ae) {
380      parser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
381      return 1;
382    }
383
384    // If the usage/version argument was provided, then we don't need
385    // to do anything else.
386    if (parser.usageOrVersionDisplayed()) {
387      return 0;
388    }
389
390    // Checks the version - if upgrade required, the tool is unusable
391    try
392    {
393      BuildVersion.checkVersionMismatch();
394    }
395    catch (InitializationException e)
396    {
397      printWrappedText(err, e.getMessageObject());
398      return 1;
399    }
400
401    // Only initialize the server when run as a standalone
402    // application.
403    if (initializeServer) {
404      // Perform the initial bootstrap of the Directory Server and process the
405      // configuration.
406      DirectoryServer directoryServer = DirectoryServer.getInstance();
407      try
408      {
409        DirectoryServer.bootstrapClient();
410        DirectoryServer.initializeJMX();
411      }
412      catch (Exception e)
413      {
414        printWrappedText(err, ERR_SERVER_BOOTSTRAP_ERROR.get(getExceptionMessage(e)));
415        return 1;
416      }
417
418      try
419      {
420        directoryServer.initializeConfiguration(configClass.getValue(),
421                                                configFile.getValue());
422      }
423      catch (InitializationException ie)
424      {
425        printWrappedText(err, ERR_CANNOT_LOAD_CONFIG.get(ie.getMessage()));
426        return 1;
427      }
428      catch (Exception e)
429      {
430        printWrappedText(err, ERR_CANNOT_LOAD_CONFIG.get(getExceptionMessage(e)));
431        return 1;
432      }
433
434
435
436      // Initialize the Directory Server schema elements.
437      try
438      {
439        directoryServer.initializeSchema();
440      }
441      catch (ConfigException | InitializationException e)
442      {
443        printWrappedText(err, ERR_CANNOT_LOAD_SCHEMA.get(e.getMessage()));
444        return 1;
445      }
446      catch (Exception e)
447      {
448        printWrappedText(err, ERR_CANNOT_LOAD_SCHEMA.get(getExceptionMessage(e)));
449        return 1;
450      }
451
452
453
454      // Initialize the Directory Server core configuration.
455      try
456      {
457        CoreConfigManager coreConfigManager = new CoreConfigManager(directoryServer.getServerContext());
458        coreConfigManager.initializeCoreConfig();
459      }
460      catch (ConfigException | InitializationException e)
461      {
462        printWrappedText(err, ERR_CANNOT_INITIALIZE_CORE_CONFIG.get(e.getMessage()));
463        return 1;
464      }
465      catch (Exception e)
466      {
467        printWrappedText(err, ERR_CANNOT_INITIALIZE_CORE_CONFIG.get(getExceptionMessage(e)));
468        return 1;
469      }
470
471
472      // Initialize the Directory Server crypto manager.
473      try
474      {
475        directoryServer.initializeCryptoManager();
476      }
477      catch (ConfigException | InitializationException e)
478      {
479        printWrappedText(err, ERR_CANNOT_INITIALIZE_CRYPTO_MANAGER.get(e.getMessage()));
480        return 1;
481      }
482      catch (Exception e)
483      {
484        printWrappedText(err, ERR_CANNOT_INITIALIZE_CRYPTO_MANAGER.get(getExceptionMessage(e)));
485        return 1;
486      }
487    }
488
489    // Make sure that we have a sub-command.
490    if (parser.getSubCommand() == null)
491    {
492      parser.displayMessageAndUsageReference(err, ERR_DBTEST_MISSING_SUBCOMMAND.get());
493      return 1;
494    }
495
496    // Retrieve the sub-command implementation and run it.
497    SubCommand subCommand = parser.getSubCommand();
498    try {
499      if("list-root-containers".equals(subCommand.getName()))
500      {
501        return listRootContainers();
502      }
503      else if("list-entry-containers".equals(subCommand.getName()))
504      {
505        return listEntryContainers(subCommand.getArgument("backendid"));
506      }
507      else if("list-database-containers".equals(subCommand.getName()))
508      {
509        return listDatabaseContainers(subCommand.getArgument("backendid"),
510                                      subCommand.getArgument("basedn"));
511      }
512      else if("dump-database-container".equals(subCommand.getName()))
513      {
514        return dumpDatabaseContainer(subCommand.getArgument("backendid"),
515                                     subCommand.getArgument("basedn"),
516                                     subCommand.getArgument("databasename"),
517                                     subCommand.getArgument("skipdecode"),
518                                     subCommand.getArgument("statsonly"),
519                                     subCommand.getArgument("maxkeyvalue"),
520                                     subCommand.getArgument("minkeyvalue"),
521                                     subCommand.getArgument("maxdatasize"),
522                                     subCommand.getArgument("mindatasize"));
523      }
524      else if("list-index-status".equals(subCommand.getName()))
525      {
526        return listIndexStatus(subCommand.getArgument("backendid"),
527                                      subCommand.getArgument("basedn"));
528      }
529      return 0;
530    } catch (Exception e) {
531      printWrappedText(err, LocalizableMessage.raw(StaticUtils.stackTraceToString(e)));
532      return 1;
533    }
534  }
535
536  private int listRootContainers()
537  {
538    final Map<LocalDBBackendCfg, BackendImpl> jeBackends = getJEBackends(null);
539    int count = 0;
540
541    // Create a table of their properties.
542    TableBuilder builder = new TableBuilder();
543
544    builder.appendHeading(INFO_LABEL_DBTEST_BACKEND_ID.get());
545    builder.appendHeading(INFO_LABEL_DBTEST_DB_DIRECTORY.get());
546
547    for(Map.Entry<LocalDBBackendCfg, BackendImpl> backend :
548        jeBackends.entrySet())
549    {
550      builder.startRow();
551      builder.appendCell(backend.getValue().getBackendID());
552      builder.appendCell(backend.getKey().getDBDirectory());
553      count++;
554    }
555
556    TextTablePrinter printer = new TextTablePrinter(out);
557    builder.print(printer);
558    out.format("%nTotal: %d%n", count);
559
560    return 0;
561  }
562
563  private int listEntryContainers(Argument backendID)
564  {
565    BackendImpl backend = getBackendById(backendID);
566    if(backend == null)
567    {
568      return 1;
569    }
570
571    // Acquire an shared lock for the backend.
572    try
573    {
574      String lockFile = LockFileManager.getBackendLockFileName(backend);
575      StringBuilder failureReason = new StringBuilder();
576      if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
577      {
578        printWrappedText(err, ERR_DBTEST_CANNOT_LOCK_BACKEND.get(backend.getBackendID(), failureReason));
579        return 1;
580      }
581    }
582    catch (Exception e)
583    {
584      printWrappedText(err, ERR_DBTEST_CANNOT_LOCK_BACKEND.get(backend.getBackendID(), getExceptionMessage(e)));
585      return 1;
586    }
587
588    RootContainer rc;
589    try
590    {
591      rc = backend.getReadOnlyRootContainer();
592    }
593    catch(Exception e)
594    {
595      printWrappedText(
596          err, ERR_DBTEST_ERROR_INITIALIZING_BACKEND.get(backend.getBackendID(), stackTraceToSingleLineString(e)));
597      return 1;
598    }
599
600    try
601    {
602      // Create a table of their properties.
603      TableBuilder builder = new TableBuilder();
604      int count = 0;
605
606      builder.appendHeading(INFO_LABEL_DBTEST_BASE_DN.get());
607      builder.appendHeading(INFO_LABEL_DBTEST_JE_DATABASE_PREFIX.get());
608      builder.appendHeading(INFO_LABEL_DBTEST_ENTRY_COUNT.get());
609
610      for(EntryContainer ec : rc.getEntryContainers())
611      {
612        builder.startRow();
613        builder.appendCell(ec.getBaseDN());
614        builder.appendCell(ec.getDatabasePrefix());
615        builder.appendCell(ec.getEntryCount());
616        count++;
617      }
618
619      TextTablePrinter printer = new TextTablePrinter(out);
620      builder.print(printer);
621      out.format("%nTotal: %d%n", count);
622
623      return 0;
624    }
625    catch(DatabaseException de)
626    {
627      printWrappedText(err, ERR_DBTEST_ERROR_READING_DATABASE.get(stackTraceToSingleLineString(de)));
628      return 1;
629    }
630    finally
631    {
632      close(rc);
633      releaseSharedLock(backend);
634    }
635  }
636
637  private int listDatabaseContainers(Argument backendID, Argument baseDN)
638  {
639    BackendImpl backend = getBackendById(backendID);
640    if(backend == null)
641    {
642      return 1;
643    }
644
645    DN base = null;
646    if(baseDN.isPresent())
647    {
648      try
649      {
650        base = DN.valueOf(baseDN.getValue());
651      }
652      catch(DirectoryException de)
653      {
654        printWrappedText(err, ERR_DBTEST_DECODE_BASE_DN.get(baseDN.getValue(), getExceptionMessage(de)));
655        return 1;
656      }
657    }
658
659    // Acquire an shared lock for the backend.
660    try
661    {
662      String lockFile = LockFileManager.getBackendLockFileName(backend);
663      StringBuilder failureReason = new StringBuilder();
664      if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
665      {
666        printWrappedText(err, ERR_DBTEST_CANNOT_LOCK_BACKEND.get(backend.getBackendID(), failureReason));
667        return 1;
668      }
669    }
670    catch (Exception e)
671    {
672      printWrappedText(err, ERR_DBTEST_CANNOT_LOCK_BACKEND.get(backend.getBackendID(), getExceptionMessage(e)));
673      return 1;
674    }
675
676    RootContainer rc;
677    try
678    {
679      rc = backend.getReadOnlyRootContainer();
680    }
681    catch(Exception e)
682    {
683      printWrappedText(
684          err, ERR_DBTEST_ERROR_INITIALIZING_BACKEND.get(backend.getBackendID(), stackTraceToSingleLineString(e)));
685      return 1;
686    }
687
688
689    try
690    {
691      // Create a table of their properties.
692      TableBuilder builder = new TableBuilder();
693      int count = 0;
694
695      builder.appendHeading(INFO_LABEL_DBTEST_DATABASE_NAME.get());
696      builder.appendHeading(INFO_LABEL_DBTEST_DATABASE_TYPE.get());
697      builder.appendHeading(INFO_LABEL_DBTEST_JE_DATABASE_NAME.get());
698      builder.appendHeading(INFO_LABEL_DBTEST_ENTRY_COUNT.get());
699
700      if(base != null)
701      {
702        EntryContainer ec = rc.getEntryContainer(base);
703        if(ec == null)
704        {
705          printWrappedText(err, ERR_DBTEST_NO_ENTRY_CONTAINERS_FOR_BASE_DN.get(base, backend.getBackendID()));
706          return 1;
707        }
708
709        count = appendDatabaseContainerRows(builder, ec, count);
710      }
711      else
712      {
713        for(EntryContainer ec : rc.getEntryContainers())
714        {
715          builder.startRow();
716          builder.appendCell("Base DN: " + ec.getBaseDN());
717          count = appendDatabaseContainerRows(builder, ec, count);
718        }
719      }
720
721      TextTablePrinter printer = new TextTablePrinter(out);
722      builder.print(printer);
723      out.format("%nTotal: %d%n", count);
724
725      return 0;
726    }
727    catch(DatabaseException de)
728    {
729      printWrappedText(err, ERR_DBTEST_ERROR_READING_DATABASE.get(stackTraceToSingleLineString(de)));
730      return 1;
731    }
732    finally
733    {
734      close(rc);
735      releaseSharedLock(backend);
736    }
737  }
738
739  private int appendDatabaseContainerRows(TableBuilder builder, EntryContainer ec, int count)
740  {
741    ArrayList<DatabaseContainer> databaseContainers = new ArrayList<>();
742    ec.listDatabases(databaseContainers);
743    String toReplace = ec.getDatabasePrefix() + "_";
744    for(DatabaseContainer dc : databaseContainers)
745    {
746      builder.startRow();
747      builder.appendCell(dc.getName().replace(toReplace, ""));
748      builder.appendCell(dc.getClass().getSimpleName());
749      builder.appendCell(dc.getName());
750      builder.appendCell(dc.getRecordCount());
751      count++;
752    }
753    return count;
754  }
755
756  private void close(RootContainer rc)
757  {
758    try
759    {
760      rc.close();
761    }
762    catch(DatabaseException ignored)
763    {
764      // Ignore.
765    }
766  }
767
768  private void releaseSharedLock(BackendImpl backend)
769  {
770    try
771    {
772      String lockFile = LockFileManager.getBackendLockFileName(backend);
773      StringBuilder failureReason = new StringBuilder();
774      if (!LockFileManager.releaseLock(lockFile, failureReason))
775      {
776        printWrappedText(err, WARN_DBTEST_CANNOT_UNLOCK_BACKEND.get(backend.getBackendID(), failureReason));
777      }
778    }
779    catch (Exception e)
780    {
781      printWrappedText(err, WARN_DBTEST_CANNOT_UNLOCK_BACKEND.get(backend.getBackendID(), getExceptionMessage(e)));
782    }
783  }
784
785  private BackendImpl getBackendById(Argument backendId)
786  {
787    final String backendID = backendId.getValue();
788    final List<Backend<?>> otherBackends = new ArrayList<>();
789    final Map<LocalDBBackendCfg, BackendImpl> jeBackends = getJEBackends(otherBackends);
790
791    for (BackendImpl b : jeBackends.values())
792    {
793      if (b.getBackendID().equalsIgnoreCase(backendID))
794      {
795        return b;
796      }
797    }
798
799    for (Backend<?> b : otherBackends)
800    {
801      if (b.getBackendID().equalsIgnoreCase(backendID))
802      {
803        printWrappedText(err, ERR_DBTEST_NOT_JE_BACKEND.get(backendID));
804        return null;
805      }
806    }
807    printWrappedText(err, ERR_DBTEST_NO_BACKENDS_FOR_ID.get(backendID));
808    return null;
809  }
810
811  private int listIndexStatus(Argument backendID, Argument baseDN)
812  {
813    BackendImpl backend = getBackendById(backendID);
814    if(backend == null)
815    {
816      return 1;
817    }
818
819    DN base = null;
820    if(baseDN.isPresent())
821    {
822      try
823      {
824        base = DN.valueOf(baseDN.getValue());
825      }
826      catch(DirectoryException de)
827      {
828        printWrappedText(err, ERR_DBTEST_DECODE_BASE_DN.get(baseDN.getValue(), getExceptionMessage(de)));
829        return 1;
830      }
831    }
832
833    // Acquire an shared lock for the backend.
834    try
835    {
836      String lockFile = LockFileManager.getBackendLockFileName(backend);
837      StringBuilder failureReason = new StringBuilder();
838      if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
839      {
840        printWrappedText(err, ERR_DBTEST_CANNOT_LOCK_BACKEND.get(backend.getBackendID(), failureReason));
841        return 1;
842      }
843    }
844    catch (Exception e)
845    {
846      printWrappedText(err, ERR_DBTEST_CANNOT_LOCK_BACKEND.get(backend.getBackendID(), getExceptionMessage(e)));
847      return 1;
848    }
849
850    RootContainer rc;
851    try
852    {
853      rc = backend.getReadOnlyRootContainer();
854    }
855    catch(Exception e)
856    {
857      printWrappedText(
858          err, ERR_DBTEST_ERROR_INITIALIZING_BACKEND.get(backend.getBackendID(), stackTraceToSingleLineString(e)));
859      return 1;
860    }
861
862
863    try
864    {
865      // Create a table of their properties.
866      TableBuilder builder = new TableBuilder();
867      int count = 0;
868
869      builder.appendHeading(INFO_LABEL_DBTEST_INDEX_NAME.get());
870      builder.appendHeading(INFO_LABEL_DBTEST_INDEX_TYPE.get());
871      builder.appendHeading(INFO_LABEL_DBTEST_JE_DATABASE_NAME.get());
872      builder.appendHeading(INFO_LABEL_DBTEST_INDEX_STATUS.get());
873      builder.appendHeading(INFO_LABEL_DBTEST_JE_RECORD_COUNT.get());
874      builder.appendHeading(
875          INFO_LABEL_DBTEST_INDEX_UNDEFINED_RECORD_COUNT.get());
876      builder.appendHeading(LocalizableMessage.raw("95%"));
877      builder.appendHeading(LocalizableMessage.raw("90%"));
878      builder.appendHeading(LocalizableMessage.raw("85%"));
879
880
881      EntryContainer ec = rc.getEntryContainer(base);
882      if(ec == null)
883      {
884        printWrappedText(err, ERR_DBTEST_NO_ENTRY_CONTAINERS_FOR_BASE_DN.get(base, backend.getBackendID()));
885        return 1;
886      }
887
888      ArrayList<DatabaseContainer> databaseContainers = new ArrayList<>();
889      Map<Index, StringBuilder> undefinedKeys = new HashMap<>();
890      ec.listDatabases(databaseContainers);
891      String toReplace = ec.getDatabasePrefix() + "_";
892      for(DatabaseContainer dc : databaseContainers)
893      {
894        if(dc instanceof Index || dc instanceof VLVIndex)
895        {
896          builder.startRow();
897          builder.appendCell(dc.getName().replace(toReplace, ""));
898          builder.appendCell(dc.getClass().getSimpleName());
899          builder.appendCell(dc.getName());
900          builder.appendCell(ec.getState().getIndexTrustState(null, dc));
901          builder.appendCell(dc.getRecordCount());
902
903          if(dc instanceof Index)
904          {
905            Index index = (Index)dc;
906            long undefined = 0, ninetyFive = 0, ninety = 0, eighty = 0;
907            DatabaseEntry key = new DatabaseEntry();
908            DatabaseEntry data = new DatabaseEntry();
909            LockMode lockMode = LockMode.DEFAULT;
910            OperationStatus status;
911
912            Cursor cursor = dc.openCursor(null, CursorConfig.DEFAULT);
913            status = cursor.getFirst(key, data, lockMode);
914            while(status == OperationStatus.SUCCESS)
915            {
916              byte[] bytes = data.getData();
917              if (bytes.length == 0 || (bytes[0] & 0x80) == 0x80)
918              {
919                // Entry limit has exceeded and there is no encoded
920                //  undefined set size.
921                undefined ++;
922                StringBuilder keyList = undefinedKeys.get(index);
923                if(keyList == null)
924                {
925                  keyList = new StringBuilder();
926                  undefinedKeys.put(index, keyList);
927                }
928                else
929                {
930                  keyList.append(" ");
931                }
932                if(index == ec.getID2Children() || index == ec.getID2Subtree())
933                {
934                  keyList.append("[").append(
935                    JebFormat.entryIDFromDatabase(key.getData())).append("]");
936                }
937                else
938                {
939                  keyList.append("[").append(
940                    new String(key.getData())).append("]");
941                }
942              }
943              else
944              {
945                // Seems like entry limit has not been exceeded and the bytes
946                // is a list of entry IDs.
947                double percentFull =
948                    (bytes.length / (double)8) / index.getIndexEntryLimit();
949                if(percentFull >= .8)
950                {
951                  if(percentFull < .9)
952                  {
953                    eighty++;
954                  }
955                  else if(percentFull < .95)
956                  {
957                    ninety++;
958                  }
959                  else
960                  {
961                    ninetyFive++;
962                  }
963                }
964              }
965              status = cursor.getNext(key, data, lockMode);
966            }
967            builder.appendCell(undefined);
968            builder.appendCell(ninetyFive);
969            builder.appendCell(ninety);
970            builder.appendCell(eighty);
971            cursor.close();
972          }
973          else
974          {
975            builder.appendCell("-");
976            builder.appendCell("-");
977            builder.appendCell("-");
978            builder.appendCell("-");
979          }
980
981          count++;
982        }
983      }
984
985      TextTablePrinter printer = new TextTablePrinter(out);
986      builder.print(printer);
987      out.format("%nTotal: %d%n", count);
988      for(Map.Entry<Index, StringBuilder> e : undefinedKeys.entrySet())
989      {
990        out.format("%nIndex: %s%n", e.getKey().getName().replace(toReplace, ""));
991        out.format("Undefined keys: %s%n", e.getValue().toString());
992      }
993      return 0;
994    }
995    catch(DatabaseException de)
996    {
997      printWrappedText(err, ERR_DBTEST_ERROR_READING_DATABASE.get(stackTraceToSingleLineString(de)));
998      return 1;
999    }
1000    finally
1001    {
1002      close(rc);
1003      releaseSharedLock(backend);
1004    }
1005  }
1006
1007  private int dumpDatabaseContainer(Argument backendID, Argument baseDN,
1008                                    Argument databaseName, Argument skipDecode,
1009                                    Argument statsOnly,
1010                                    Argument maxKeyValue, Argument minKeyValue,
1011                                    Argument maxDataSize, Argument minDataSize)
1012  {
1013    BackendImpl backend = getBackendById(backendID);
1014    if(backend == null)
1015    {
1016      return 1;
1017    }
1018
1019    DN base = null;
1020    try
1021    {
1022      base = DN.valueOf(baseDN.getValue());
1023    }
1024    catch(DirectoryException de)
1025    {
1026      printWrappedText(err, ERR_DBTEST_DECODE_BASE_DN.get(baseDN.getValue(), getExceptionMessage(de)));
1027      return 1;
1028    }
1029
1030    // Acquire an shared lock for the backend.
1031    try
1032    {
1033      String lockFile = LockFileManager.getBackendLockFileName(backend);
1034      StringBuilder failureReason = new StringBuilder();
1035      if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
1036      {
1037        printWrappedText(err, ERR_DBTEST_CANNOT_LOCK_BACKEND.get(backend.getBackendID(), failureReason));
1038        return 1;
1039      }
1040    }
1041    catch (Exception e)
1042    {
1043      printWrappedText(err, ERR_DBTEST_CANNOT_LOCK_BACKEND.get(backend.getBackendID(), getExceptionMessage(e)));
1044      return 1;
1045    }
1046
1047    RootContainer rc;
1048    try
1049    {
1050      rc = backend.getReadOnlyRootContainer();
1051    }
1052    catch(Exception e)
1053    {
1054      printWrappedText(
1055          err, ERR_DBTEST_ERROR_INITIALIZING_BACKEND.get(backend.getBackendID(), stackTraceToSingleLineString(e)));
1056      return 1;
1057    }
1058
1059    try
1060    {
1061      EntryContainer ec = rc.getEntryContainer(base);
1062      if(ec == null)
1063      {
1064        printWrappedText(err, ERR_DBTEST_NO_ENTRY_CONTAINERS_FOR_BASE_DN.get(base, backend.getBackendID()));
1065        return 1;
1066      }
1067
1068      DatabaseContainer databaseContainer = null;
1069      ArrayList<DatabaseContainer> databaseContainers = new ArrayList<>();
1070      ec.listDatabases(databaseContainers);
1071      String toReplace = ec.getDatabasePrefix() + "_";
1072      for(DatabaseContainer dc : databaseContainers)
1073      {
1074        if(dc.getName().replace(toReplace, "").equalsIgnoreCase(databaseName.getValue()))
1075        {
1076          databaseContainer = dc;
1077          break;
1078        }
1079      }
1080
1081      if(databaseContainer == null)
1082      {
1083        printWrappedText(
1084            err, ERR_DBTEST_NO_DATABASE_CONTAINERS_FOR_NAME.get(databaseName.getValue(), base, backend.getBackendID()));
1085        return 1;
1086      }
1087
1088      int count = 0;
1089      long totalKeySize = 0;
1090      long totalDataSize = 0;
1091      int indent = 4;
1092
1093      Cursor cursor = databaseContainer.openCursor(null, CursorConfig.DEFAULT);
1094      try
1095      {
1096        DatabaseEntry key = new DatabaseEntry();
1097        DatabaseEntry data = new DatabaseEntry();
1098        LockMode lockMode = LockMode.DEFAULT;
1099        OperationStatus status;
1100        byte[] start = null;
1101        byte[] end = null;
1102        int minSize = -1;
1103        int maxSize = -1;
1104
1105        if(maxDataSize.isPresent())
1106        {
1107          try
1108          {
1109            maxSize = maxDataSize.getIntValue();
1110          }
1111          catch(Exception e)
1112          {
1113            printWrappedText(err, ERR_DBTEST_CANNOT_DECODE_SIZE.get(maxDataSize.getValue(), getExceptionMessage(e)));
1114            return 1;
1115          }
1116        }
1117
1118        if(minDataSize.isPresent())
1119        {
1120          try
1121          {
1122            minSize = minDataSize.getIntValue();
1123          }
1124          catch(Exception e)
1125          {
1126            printWrappedText(err, ERR_DBTEST_CANNOT_DECODE_SIZE.get(minDataSize.getValue(), getExceptionMessage(e)));
1127            return 1;
1128          }
1129        }
1130
1131        // Parse the min value if given
1132        if(minKeyValue.isPresent())
1133        {
1134          try
1135          {
1136            start = parseKeyValue(minKeyValue.getValue(), databaseContainer);
1137          }
1138          catch(Exception e)
1139          {
1140            printWrappedText(err, ERR_DBTEST_CANNOT_DECODE_KEY.get(minKeyValue.getValue(), getExceptionMessage(e)));
1141            return 1;
1142          }
1143        }
1144
1145        // Parse the max value if given
1146        if(maxKeyValue.isPresent())
1147        {
1148          try
1149          {
1150            end = parseKeyValue(maxKeyValue.getValue(), databaseContainer);
1151          }
1152          catch(Exception e)
1153          {
1154            printWrappedText(err, ERR_DBTEST_CANNOT_DECODE_KEY.get(maxKeyValue.getValue(), getExceptionMessage(e)));
1155            return 1;
1156          }
1157        }
1158
1159
1160        if(start != null)
1161        {
1162          key.setData(start);
1163          status = cursor.getSearchKey(key, data, lockMode);
1164        }
1165        else
1166        {
1167          status = cursor.getFirst(key, data, lockMode);
1168        }
1169
1170        final String lineSep = System.getProperty("line.separator");
1171        while(status == OperationStatus.SUCCESS)
1172        {
1173          // Make sure this record is within the value size params
1174          if((minSize > 0 && data.getSize() < minSize) ||
1175              (maxSize > 0 && data.getSize() > maxSize))
1176          {
1177            status = cursor.getNext(key, data, lockMode);
1178            continue;
1179          }
1180
1181          // Make sure we haven't gone pass the max value yet
1182          if(end != null
1183              && getComparator(databaseContainer).compare(key.getData(), end) > 0)
1184          {
1185            break;
1186          }
1187
1188          if (!statsOnly.isPresent())
1189          {
1190            LocalizableMessage keyLabel = INFO_LABEL_DBTEST_KEY.get();
1191            LocalizableMessage dataLabel = INFO_LABEL_DBTEST_DATA.get();
1192
1193            String formatedKey = null;
1194            String formatedData = null;
1195
1196            if(!skipDecode.isPresent())
1197            {
1198              if(databaseContainer instanceof DN2ID)
1199              {
1200                try
1201                {
1202                  formatedKey = new String(key.getData()) + ec.getBaseDN();
1203                  keyLabel = INFO_LABEL_DBTEST_ENTRY_DN.get();
1204                }
1205                catch(Exception e)
1206                {
1207                  printWrappedText(err, ERR_DBTEST_DECODE_FAIL.get(getExceptionMessage(e)));
1208                }
1209                formatedData = String.valueOf(
1210                  JebFormat.entryIDFromDatabase(data.getData()));
1211                dataLabel = INFO_LABEL_DBTEST_ENTRY_ID.get();
1212              }
1213              else if(databaseContainer instanceof ID2Entry)
1214              {
1215                formatedKey = String.valueOf(
1216                    JebFormat.entryIDFromDatabase(key.getData()));
1217                keyLabel = INFO_LABEL_DBTEST_ENTRY_ID.get();
1218                try
1219                {
1220                  formatedData = lineSep +
1221                      ID2Entry.entryFromDatabase(
1222                        ByteString.wrap(data.getData()),
1223                        ec.getRootContainer().getCompressedSchema()).toLDIFString();
1224                  dataLabel = INFO_LABEL_DBTEST_ENTRY.get();
1225                }
1226                catch(Exception e)
1227                {
1228                  printWrappedText(err, ERR_DBTEST_DECODE_FAIL.get(getExceptionMessage(e)));
1229                }
1230              }
1231              else if(databaseContainer instanceof DN2URI)
1232              {
1233                try
1234                {
1235                  formatedKey = new String(key.getData());
1236                  keyLabel = INFO_LABEL_DBTEST_ENTRY_DN.get();
1237                }
1238                catch(Exception e)
1239                {
1240                  printWrappedText(err, ERR_DBTEST_DECODE_FAIL.get(getExceptionMessage(e)));
1241                }
1242                formatedData = new String(key.getData());
1243                dataLabel = INFO_LABEL_DBTEST_URI.get();
1244              }
1245              else if(databaseContainer instanceof Index)
1246              {
1247                formatedKey = new String(key.getData());
1248                keyLabel = INFO_LABEL_DBTEST_INDEX_VALUE.get();
1249
1250                EntryIDSet idSet = new EntryIDSet(key.getData(), data.getData());
1251                if(idSet.isDefined())
1252                {
1253                  int lineCount = 0;
1254                  StringBuilder builder = new StringBuilder();
1255
1256                  for (EntryID entryID : idSet)
1257                  {
1258                    builder.append(entryID);
1259                    if(lineCount == 10)
1260                    {
1261                      builder.append(lineSep);
1262                      lineCount = 0;
1263                    }
1264                    else
1265                    {
1266                      builder.append(" ");
1267                      lineCount++;
1268                    }
1269                  }
1270                  formatedData = builder.toString();
1271                }
1272                else
1273                {
1274                  formatedData = idSet.toString();
1275                }
1276                dataLabel = INFO_LABEL_DBTEST_INDEX_ENTRY_ID_LIST.get();
1277              }
1278              else if(databaseContainer instanceof VLVIndex)
1279              {
1280                VLVIndex index = (VLVIndex)databaseContainer;
1281                SortKey[] sortKeys = index.sortOrder.getSortKeys();
1282
1283                int pos = 0;
1284                byte[] keyBytes = key.getData();
1285                if(keyBytes.length > 0)
1286                {
1287                  StringBuilder builder = new StringBuilder();
1288
1289                  // Decode the attribute values
1290                  for(SortKey sortKey : sortKeys)
1291                  {
1292                    int valueLength = keyBytes[pos] & 0x7F;
1293                    if (keyBytes[pos++] != valueLength)
1294                    {
1295                      int numLengthBytes = valueLength;
1296                      valueLength = 0;
1297                      for (int k=0; k < numLengthBytes; k++, pos++)
1298                      {
1299                        valueLength = (valueLength << 8) |
1300                            (keyBytes[pos] & 0xFF);
1301                      }
1302                    }
1303
1304                    byte[] valueBytes = new byte[valueLength];
1305                    System.arraycopy(keyBytes, pos, valueBytes, 0, valueLength);
1306                    builder.append(sortKey.getAttributeType().getNameOrOID());
1307                    builder.append(": ");
1308                    if(valueBytes.length == 0)
1309                    {
1310                      builder.append("NULL");
1311                    }
1312                    else
1313                    {
1314                      builder.append(new String(valueBytes));
1315                    }
1316                    builder.append(" ");
1317                    pos += valueLength;
1318                  }
1319
1320                  byte[] entryIDBytes = new byte[8];
1321                  System.arraycopy(keyBytes, pos, entryIDBytes, 0,
1322                                   entryIDBytes.length);
1323                  long entryID = JebFormat.entryIDFromDatabase(entryIDBytes);
1324
1325                  formatedKey = lineSep + entryID + ": " + builder;
1326                }
1327                else
1328                {
1329                  formatedKey = "UNBOUNDED";
1330                }
1331                keyLabel = INFO_LABEL_DBTEST_VLV_INDEX_LAST_SORT_KEYS.get();
1332
1333                try
1334                {
1335                  StringBuilder builder = new StringBuilder();
1336                  SortValuesSet svs = new SortValuesSet(key.getData(),
1337                                                      data.getData(),
1338                                                      index);
1339                  long[] entryIDs = svs.getEntryIDs();
1340                  for(int i = 0; i < entryIDs.length; i++)
1341                  {
1342                    builder.append(entryIDs[i]);
1343                    builder.append(": ");
1344                    for(int j = 0; j < sortKeys.length; j++)
1345                    {
1346                      SortKey sortKey = index.sortOrder.getSortKeys()[j];
1347                      ByteString value = svs.getValue(i * sortKeys.length + j);
1348                      builder.append(sortKey.getAttributeType().getNameOrOID());
1349                      builder.append(": ");
1350                      if(value == null)
1351                      {
1352                        builder.append("NULL");
1353                      }
1354                      else if(value.length() == 0)
1355                      {
1356                        builder.append("SIZE-EXCEEDED");
1357                      }
1358                      else
1359                      {
1360                        builder.append(value);
1361                      }
1362                      builder.append(" ");
1363                    }
1364                    builder.append(lineSep);
1365                  }
1366                  formatedData = lineSep + builder;
1367                  dataLabel = INFO_LABEL_DBTEST_INDEX_ENTRY_ID_LIST.get();
1368                }
1369                catch(Exception e)
1370                {
1371                  printWrappedText(err, ERR_DBTEST_DECODE_FAIL.get(getExceptionMessage(e)));
1372                }
1373              }
1374            }
1375
1376            if(formatedKey == null)
1377            {
1378              StringBuilder keyBuilder = new StringBuilder();
1379              StaticUtils.byteArrayToHexPlusAscii(keyBuilder, key.getData(), indent);
1380              formatedKey = lineSep + keyBuilder;
1381            }
1382            if(formatedData == null)
1383            {
1384              StringBuilder dataBuilder = new StringBuilder();
1385              StaticUtils.byteArrayToHexPlusAscii(dataBuilder, data.getData(), indent);
1386              formatedData = lineSep + dataBuilder;
1387            }
1388
1389            out.format("%s (%d bytes): %s%n", keyLabel,
1390                       key.getData().length, formatedKey);
1391            out.format("%s (%d bytes): %s%n%n", dataLabel,
1392                       data.getData().length, formatedData);
1393          }
1394          status = cursor.getNext(key, data, lockMode);
1395          count++;
1396          totalKeySize += key.getData().length;
1397          totalDataSize += data.getData().length;
1398        }
1399      }
1400      finally
1401      {
1402        cursor.close();
1403      }
1404      out.format("%nTotal Records: %d%n", count);
1405      if(count > 0)
1406      {
1407        out.format("Total / Average Key Size: %d bytes / %d bytes%n",
1408                   totalKeySize, totalKeySize / count);
1409        out.format("Total / Average Data Size: %d bytes / %d bytes%n",
1410                   totalDataSize, totalDataSize / count);
1411      }
1412      return 0;
1413    }
1414    catch(DatabaseException de)
1415    {
1416      printWrappedText(err, ERR_DBTEST_ERROR_READING_DATABASE.get(stackTraceToSingleLineString(de)));
1417      return 1;
1418    }
1419    finally
1420    {
1421      close(rc);
1422      releaseSharedLock(backend);
1423    }
1424  }
1425
1426  private byte[] parseKeyValue(String value, DatabaseContainer databaseContainer)
1427      throws ParseException, DirectoryException
1428  {
1429    if(value.startsWith("0x"))
1430    {
1431      return hexStringToByteArray(value.substring(2));
1432    }
1433    else if(databaseContainer instanceof DN2ID
1434        || databaseContainer instanceof DN2URI)
1435    {
1436      // Encode the value as a DN
1437      return DN.valueOf(value).toNormalizedByteString().toByteArray();
1438    }
1439    else if(databaseContainer instanceof ID2Entry)
1440    {
1441      // Encode the value as an entryID
1442      return JebFormat.entryIDToDatabase(
1443          Long.parseLong(value));
1444    }
1445    else if(databaseContainer instanceof VLVIndex)
1446    {
1447      // Encode the value as a size/value pair
1448      byte[] vBytes = StaticUtils.getBytes(value);
1449      ByteStringBuilder builder = new ByteStringBuilder();
1450      builder.appendBERLength(vBytes.length);
1451      builder.append(vBytes);
1452      return builder.toByteArray();
1453    }
1454    else
1455    {
1456      return StaticUtils.getBytes(value);
1457    }
1458  }
1459
1460  private Comparator<byte[]> getComparator(DatabaseContainer databaseContainer)
1461  {
1462    if(databaseContainer instanceof Index)
1463    {
1464      return ((Index) databaseContainer).getComparator();
1465    }
1466    else if(databaseContainer instanceof VLVIndex)
1467    {
1468      return ((VLVIndex)databaseContainer).comparator;
1469    }
1470    else
1471    { // default comparator
1472      return ByteSequence.BYTE_ARRAY_COMPARATOR;
1473    }
1474  }
1475
1476  private static Map<LocalDBBackendCfg, BackendImpl> getJEBackends(Collection<Backend<?>> otherBackends)
1477  {
1478    ArrayList<Backend> backendList = new ArrayList<>();
1479    ArrayList<BackendCfg> entryList = new ArrayList<>();
1480    ArrayList<List<DN>> dnList = new ArrayList<>();
1481    BackendToolUtils.getBackends(backendList, entryList, dnList);
1482
1483    final Map<LocalDBBackendCfg, BackendImpl> jeBackends = new LinkedHashMap<>();
1484    for(int i = 0; i < backendList.size(); i++)
1485    {
1486      Backend<?> backend = backendList.get(i);
1487      if(backend instanceof BackendImpl)
1488      {
1489        jeBackends.put((LocalDBBackendCfg)entryList.get(i),
1490                       (BackendImpl)backend);
1491      }
1492      else if (otherBackends != null)
1493      {
1494        otherBackends.add(backend);
1495      }
1496    }
1497    return jeBackends;
1498  }
1499}