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 2015 ForgeRock AS.
025 */
026package org.opends.server.backends.pluggable;
027
028import static org.opends.messages.ToolMessages.*;
029import static org.opends.server.util.StaticUtils.*;
030
031import static com.forgerock.opendj.cli.ArgumentConstants.*;
032import static com.forgerock.opendj.cli.Utils.*;
033
034import java.io.OutputStream;
035import java.io.PrintStream;
036import java.text.NumberFormat;
037import java.util.ArrayList;
038import java.util.Collection;
039import java.util.HashMap;
040import java.util.LinkedHashMap;
041import java.util.List;
042import java.util.Map;
043import java.util.SortedSet;
044import java.util.TreeSet;
045
046import org.forgerock.i18n.LocalizableMessage;
047import org.forgerock.opendj.config.SizeUnit;
048import org.forgerock.opendj.config.server.ConfigException;
049import org.forgerock.opendj.ldap.ByteString;
050import org.forgerock.util.Option;
051import org.forgerock.util.Options;
052import org.opends.server.admin.std.server.BackendCfg;
053import org.opends.server.admin.std.server.PluggableBackendCfg;
054import org.opends.server.api.Backend;
055import org.opends.server.backends.pluggable.spi.Cursor;
056import org.opends.server.backends.pluggable.spi.ReadOperation;
057import org.opends.server.backends.pluggable.spi.ReadableTransaction;
058import org.opends.server.backends.pluggable.spi.StorageRuntimeException;
059import org.opends.server.backends.pluggable.spi.TreeName;
060import org.opends.server.core.CoreConfigManager;
061import org.opends.server.core.DirectoryServer;
062import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler;
063import org.opends.server.core.LockFileManager;
064import org.opends.server.extensions.ConfigFileHandler;
065import org.opends.server.loggers.JDKLogging;
066import org.opends.server.tools.BackendToolUtils;
067import org.opends.server.types.DN;
068import org.opends.server.types.DirectoryException;
069import org.opends.server.types.InitializationException;
070import org.opends.server.types.NullOutputStream;
071import org.opends.server.util.BuildVersion;
072import org.opends.server.util.StaticUtils;
073
074import com.forgerock.opendj.cli.Argument;
075import com.forgerock.opendj.cli.ArgumentException;
076import com.forgerock.opendj.cli.BooleanArgument;
077import com.forgerock.opendj.cli.CommonArguments;
078import com.forgerock.opendj.cli.IntegerArgument;
079import com.forgerock.opendj.cli.StringArgument;
080import com.forgerock.opendj.cli.SubCommand;
081import com.forgerock.opendj.cli.SubCommandArgumentParser;
082import com.forgerock.opendj.cli.TableBuilder;
083import com.forgerock.opendj.cli.TextTablePrinter;
084
085/**
086 * This program provides a utility that may be used to debug a Pluggable Backend.
087 * This tool provides the ability to:
088 * <ul>
089 * <li>list root containers</li>
090 * <li>list entry containers</li>
091 * <li>list Trees in a Backend or Storage</li>
092 * <li>gather information about Backend indexes</li>
093 * <li>dump the contents of a Tree either at the Backend or the Storage layer.</li>
094 * </ul>
095 * This will be a process that is intended to run outside of Directory Server and not
096 * internally within the server process (e.g., via the tasks interface); it still
097 * requires configuration information and access to Directory Server instance data.
098 */
099public class BackendStat
100{
101  /**
102   * Collects all necessary interaction interfaces with either a Backend using TreeNames
103   * or a storage using Trees.
104   */
105  interface TreeKeyValue
106  {
107    /**
108     * Returns a key given a string representation of it.
109     *
110     * @param data a string representation of the key.
111     *             Prefixing with "0x" will interpret the rest of the string as an hex dump
112     *             of the intended value.
113     * @return a key given a string representation of it
114     */
115    ByteString getTreeKey(String data);
116
117    /**
118     * Returns a printable string for the given key.
119     *
120     * @param key a key from the Tree
121     * @return a printable string for the given key
122     */
123    String keyDecoder(ByteString key);
124
125    /**
126     * Returns a printable string for the given value.
127     *
128     * @param value a value from the tree
129     * @return a printable string for the given value
130     */
131    String valueDecoder(ByteString value);
132
133    /**
134     * Returns the TreeName for this storage Tree.
135     *
136     * @return the TreeName for this storage Tree
137     */
138    TreeName getTreeName();
139  }
140
141  /** Stays at the storage level when cursoring Trees. */
142  static class StorageTreeKeyValue implements TreeKeyValue
143  {
144    private TreeName treeName;
145
146    public StorageTreeKeyValue(TreeName treeName)
147    {
148      this.treeName = treeName;
149    }
150
151    @Override
152    public TreeName getTreeName()
153    {
154      return treeName;
155    }
156
157    @Override
158    public ByteString getTreeKey(String data)
159    {
160      return ByteString.valueOfUtf8(data);
161    }
162
163    @Override
164    public String keyDecoder(ByteString key)
165    {
166      throw new UnsupportedOperationException(ERR_BACKEND_TOOL_DECODER_NOT_AVAILABLE.get().toString());
167    }
168
169    @Override
170    public String valueDecoder(ByteString value)
171    {
172      throw new UnsupportedOperationException(ERR_BACKEND_TOOL_DECODER_NOT_AVAILABLE.get().toString());
173    }
174  }
175
176  /** Delegate key semantics to the backend. */
177  static class BackendTreeKeyValue implements TreeKeyValue
178  {
179    private TreeName name;
180    private Tree tree;
181
182    public BackendTreeKeyValue(Tree tree)
183    {
184      this.tree = tree;
185      this.name = tree.getName();
186    }
187
188    @Override
189    public ByteString getTreeKey(String data)
190    {
191      if (data.length() == 0)
192      {
193        return ByteString.empty();
194      }
195      return tree.generateKey(data);
196    }
197
198    @Override
199    public String keyDecoder(ByteString key)
200    {
201      return tree.keyToString(key);
202    }
203
204    @Override
205    public String valueDecoder(ByteString value)
206    {
207      return tree.valueToString(value);
208    }
209
210    @Override
211    public TreeName getTreeName()
212    {
213      return name;
214    }
215  }
216
217  /** Statistics collector. */
218  class TreeStats
219  {
220    final long count;
221    final long totalKeySize;
222    final long totalDataSize;
223
224    TreeStats(long count, long tks, long tds)
225    {
226      this.count = count;
227      this.totalKeySize = tks;
228      this.totalDataSize = tds;
229    }
230  }
231
232  private static final Option<Boolean> DUMP_DECODE_VALUE = Option.withDefault(true);
233  private static final Option<Boolean> DUMP_STATS_ONLY = Option.withDefault(false);
234  private static final Option<Boolean> DUMP_SINGLE_LINE = Option.withDefault(false);
235  private static final Option<Argument> DUMP_MIN_KEY_VALUE = Option.of(Argument.class, null);
236  private static final Option<Argument> DUMP_MAX_KEY_VALUE = Option.of(Argument.class, null);
237  private static final Option<Boolean> DUMP_MIN_KEY_VALUE_IS_HEX = Option.withDefault(false);
238  private static final Option<Boolean> DUMP_MAX_KEY_VALUE_IS_HEX = Option.withDefault(false);
239  private static final Option<Integer> DUMP_MIN_DATA_SIZE = Option.of(Integer.class, 0);
240  private static final Option<Integer> DUMP_MAX_DATA_SIZE = Option.of(Integer.class, Integer.MAX_VALUE);
241  private static final Option<Integer> DUMP_INDENT = Option.of(Integer.class, 4);
242
243  // Sub-command names.
244  private static final String LIST_BACKENDS = "list-backends";
245  private static final String LIST_BASE_DNS = "list-base-dns";
246  private static final String LIST_INDEXES = "list-indexes";
247  private static final String SHOW_INDEX_STATUS = "show-index-status";
248  private static final String DUMP_INDEX = "dump-index";
249  private static final String LIST_RAW_DBS = "list-raw-dbs";
250  private static final String DUMP_RAW_DB = "dump-raw-db";
251
252  private static final String BACKENDID_NAME = "backendid";
253  private static final String BACKENDID = "backendID";
254  private static final String BASEDN_NAME = "basedn";
255  private static final String BASEDN = "baseDN";
256  private static final String USESIUNITS_NAME = "usesiunits";
257  private static final String USESIUNITS = "useSIUnits";
258  private static final String MAXDATASIZE_NAME = "maxdatasize";
259  private static final String MAXDATASIZE = "maxDataSize";
260  private static final String MAXKEYVALUE_NAME = "maxkeyvalue";
261  private static final String MAXKEYVALUE = "maxKeyValue";
262  private static final String MAXHEXKEYVALUE_NAME = "maxhexkeyvalue";
263  private static final String MAXHEXKEYVALUE = "maxHexKeyValue";
264  private static final String MINDATASIZE_NAME = "mindatasize";
265  private static final String MINDATASIZE = "minDataSize";
266  private static final String MINKEYVALUE_NAME = "minkeyvalue";
267  private static final String MINKEYVALUE = "minKeyValue";
268  private static final String MINHEXKEYVALUE_NAME = "minhexkeyvalue";
269  private static final String MINHEXKEYVALUE = "minHexKeyValue";
270  private static final String SKIPDECODE_NAME = "skipdecode";
271  private static final String SKIPDECODE = "skipDecode";
272  private static final String STATSONLY_NAME = "statsonly";
273  private static final String STATSONLY = "statsOnly";
274  private static final String INDEXNAME_NAME = "indexname";
275  private static final String INDEXNAME = "indexName";
276  private static final String DBNAME_NAME = "dbname";
277  private static final String DBNAME = "dbName";
278  private static final String SINGLELINE_NAME = "singleline";
279  private static final String SINGLELINE = "singleLine";
280
281  private static final String HEXDUMP_LINE_FORMAT = "%s%s %s%n";
282
283  /** The error stream which this application should use. */
284  private final PrintStream err;
285  /** The output stream which this application should use. */
286  private final PrintStream out;
287
288  /** The command-line argument parser. */
289  private final SubCommandArgumentParser parser;
290  /** The argument which should be used to request usage information. */
291  private BooleanArgument showUsageArgument;
292  /** The argument which should be used to specify the config class. */
293  private StringArgument configClass;
294  /** The argument which should be used to specify the config file. */
295  private StringArgument configFile;
296
297  /** Flag indicating whether or not the sub-commands have already been initialized. */
298  private boolean subCommandsInitialized;
299  /** Flag indicating whether or not the global arguments have already been initialized. */
300  private boolean globalArgumentsInitialized;
301
302  private DirectoryServer directoryServer;
303
304  /**
305   * Provides the command-line arguments to the main application for
306   * processing.
307   *
308   * @param args The set of command-line arguments provided to this
309   *             program.
310   */
311  public static void main(String[] args)
312  {
313    int exitCode = main(args, System.out, System.err);
314    if (exitCode != 0)
315    {
316      System.exit(filterExitCode(exitCode));
317    }
318  }
319
320  /**
321   * Provides the command-line arguments to the main application for
322   * processing and returns the exit code as an integer.
323   *
324   * @param args      The set of command-line arguments provided to this
325   *                  program.
326   * @param outStream The output stream for standard output.
327   * @param errStream The output stream for standard error.
328   * @return Zero to indicate that the program completed successfully,
329   * or non-zero to indicate that an error occurred.
330   */
331  public static int main(String[] args, OutputStream outStream, OutputStream errStream)
332  {
333    BackendStat app = new BackendStat(outStream, errStream);
334    return app.run(args);
335  }
336
337  /**
338   * Creates a new dsconfig application instance.
339   *
340   * @param out The application output stream.
341   * @param err The application error stream.
342   */
343  public BackendStat(OutputStream out, OutputStream err)
344  {
345    this.out = NullOutputStream.wrapOrNullStream(out);
346    this.err = NullOutputStream.wrapOrNullStream(err);
347    JDKLogging.disableLogging();
348
349    LocalizableMessage toolDescription = INFO_DESCRIPTION_BACKEND_TOOL.get();
350    this.parser = new SubCommandArgumentParser(getClass().getName(), toolDescription, false);
351    this.parser.setShortToolDescription(REF_SHORT_DESC_BACKEND_TOOL.get());
352    this.parser.setVersionHandler(new DirectoryServerVersionHandler());
353  }
354
355  /**
356   * Registers the global arguments with the argument parser.
357   *
358   * @throws ArgumentException If a global argument could not be registered.
359   */
360  private void initializeGlobalArguments() throws ArgumentException
361  {
362    if (!globalArgumentsInitialized)
363    {
364      configClass =
365          new StringArgument("configclass", OPTION_SHORT_CONFIG_CLASS, OPTION_LONG_CONFIG_CLASS, true, false, true,
366              INFO_CONFIGCLASS_PLACEHOLDER.get(), ConfigFileHandler.class.getName(), null,
367              INFO_DESCRIPTION_CONFIG_CLASS.get());
368      configClass.setHidden(true);
369
370      configFile =
371          new StringArgument("configfile", 'f', "configFile", true, false, true, INFO_CONFIGFILE_PLACEHOLDER.get(),
372              null, null, INFO_DESCRIPTION_CONFIG_FILE.get());
373      configFile.setHidden(true);
374
375      showUsageArgument = CommonArguments.getShowUsage();
376
377      // Register the global arguments.
378      parser.addGlobalArgument(showUsageArgument);
379      parser.setUsageArgument(showUsageArgument, out);
380      parser.addGlobalArgument(configClass);
381      parser.addGlobalArgument(configFile);
382
383      globalArgumentsInitialized = true;
384    }
385  }
386
387  /**
388   * Registers the sub-commands with the argument parser.
389   *
390   * @throws ArgumentException If a sub-command could not be created.
391   */
392  private void initializeSubCommands() throws ArgumentException
393  {
394    if (!subCommandsInitialized)
395    {
396      // list-backends
397      new SubCommand(parser, LIST_BACKENDS,
398                           INFO_DESCRIPTION_BACKEND_TOOL_SUBCMD_LIST_BACKENDS.get());
399
400      // list-base-dns
401      SubCommand sub = new SubCommand(parser, LIST_BASE_DNS,
402                                      INFO_DESCRIPTION_BACKEND_DEBUG_SUBCMD_LIST_ENTRY_CONTAINERS.get());
403      addBackendArgument(sub);
404
405      // list-indexes
406      sub = new SubCommand(parser, LIST_INDEXES, INFO_DESCRIPTION_BACKEND_TOOL_SUBCMD_LIST_INDEXES.get());
407      addBackendBaseDNArguments(sub, false, false, true);
408
409      // show-index-status
410      sub = new SubCommand(parser, SHOW_INDEX_STATUS, INFO_DESCRIPTION_BACKEND_DEBUG_SUBCMD_LIST_INDEX_STATUS.get());
411      sub.setDocDescriptionSupplement(SUPPLEMENT_DESCRIPTION_BACKEND_TOOL_SUBCMD_LIST_INDEX_STATUS.get());
412      addBackendBaseDNArguments(sub, true, true, true);
413
414      // dump-index
415      sub = new SubCommand(parser, DUMP_INDEX, INFO_DESCRIPTION_BACKEND_TOOL_SUBCMD_DUMP_INDEX.get());
416      addBackendBaseDNArguments(sub, true, false, true);
417      sub.addArgument(new StringArgument(INDEXNAME_NAME, 'i', INDEXNAME, true, false, true,
418                                         INFO_INDEX_NAME_PLACEHOLDER.get(), null, null,
419                                         INFO_DESCRIPTION_BACKEND_DEBUG_INDEX_NAME.get()));
420      addDumpSubCommandArguments(sub);
421      BooleanArgument skipDecode =
422          new BooleanArgument(SKIPDECODE_NAME, 'p', SKIPDECODE, INFO_DESCRIPTION_BACKEND_DEBUG_SKIP_DECODE.get());
423      sub.addArgument(skipDecode);
424
425      // list-raw-dbs
426      sub = new SubCommand(parser, LIST_RAW_DBS, INFO_DESCRIPTION_BACKEND_TOOL_SUBCMD_LIST_RAW_DBS.get());
427      addBackendArgument(sub);
428      BooleanArgument useSIUnits =
429          new BooleanArgument(USESIUNITS_NAME, 'u', USESIUNITS, INFO_DESCRIPTION_BACKEND_TOOL_USE_SI_UNITS.get());
430      sub.addArgument(useSIUnits);
431
432      // dump-raw-db
433      sub = new SubCommand(parser, DUMP_RAW_DB, INFO_DESCRIPTION_BACKEND_TOOL_SUBCMD_DUMP_RAW_DB.get());
434      addBackendArgument(sub);
435      sub.addArgument(new StringArgument(DBNAME_NAME, 'd', DBNAME, true, false, true,
436                                         INFO_DATABASE_NAME_PLACEHOLDER.get(), null, null,
437                                         INFO_DESCRIPTION_BACKEND_DEBUG_RAW_DB_NAME.get()));
438      addDumpSubCommandArguments(sub);
439      BooleanArgument singleLine =
440          new BooleanArgument(SINGLELINE_NAME, 'l', SINGLELINE, INFO_DESCRIPTION_BACKEND_TOOL_SUBCMD_SINGLE_LINE.get());
441      sub.addArgument(singleLine);
442
443      subCommandsInitialized = true;
444    }
445  }
446
447  private void addBackendArgument(SubCommand sub) throws ArgumentException
448  {
449    sub.addArgument(
450        new StringArgument(BACKENDID_NAME, 'n', BACKENDID, true, false, true, INFO_BACKENDNAME_PLACEHOLDER.get(), null,
451            null, INFO_DESCRIPTION_BACKEND_DEBUG_BACKEND_ID.get()));
452  }
453
454  private void addBackendBaseDNArguments(SubCommand sub, boolean isRequired, boolean isMultiValued, boolean needsValue)
455      throws ArgumentException
456  {
457    addBackendArgument(sub);
458    sub.addArgument(new StringArgument(BASEDN_NAME, 'b', BASEDN, isRequired, isMultiValued, needsValue,
459        INFO_BASEDN_PLACEHOLDER.get(), null, null, INFO_DESCRIPTION_BACKEND_DEBUG_BASE_DN.get()));
460  }
461
462  private void addDumpSubCommandArguments(SubCommand sub) throws ArgumentException
463  {
464    sub.addArgument(new BooleanArgument(STATSONLY_NAME, 'q', STATSONLY,
465        INFO_DESCRIPTION_BACKEND_DEBUG_STATS_ONLY.get()));
466    sub.addArgument(newMaxKeyValueArg());
467    sub.addArgument(newMinKeyValueArg());
468    sub.addArgument(new StringArgument(MAXHEXKEYVALUE_NAME, 'X', MAXHEXKEYVALUE, false, false, true,
469        INFO_MAX_KEY_VALUE_PLACEHOLDER.get(), null, null, INFO_DESCRIPTION_BACKEND_DEBUG_MAX_KEY_VALUE.get()));
470    sub.addArgument(new StringArgument(MINHEXKEYVALUE_NAME, 'x', MINHEXKEYVALUE, false, false, true,
471        INFO_MIN_KEY_VALUE_PLACEHOLDER.get(), null, null, INFO_DESCRIPTION_BACKEND_DEBUG_MIN_KEY_VALUE.get()));
472    sub.addArgument(new IntegerArgument(MAXDATASIZE_NAME, 'S', MAXDATASIZE, false, false, true,
473        INFO_MAX_DATA_SIZE_PLACEHOLDER.get(), -1, null, INFO_DESCRIPTION_BACKEND_DEBUG_MAX_DATA_SIZE.get()));
474    sub.addArgument(new IntegerArgument(MINDATASIZE_NAME, 's', MINDATASIZE, false, false, true,
475        INFO_MIN_DATA_SIZE_PLACEHOLDER.get(), -1, null, INFO_DESCRIPTION_BACKEND_DEBUG_MIN_DATA_SIZE.get()));
476  }
477
478  private StringArgument newMinKeyValueArg() throws ArgumentException
479  {
480    return new StringArgument(MINKEYVALUE_NAME, 'k', MINKEYVALUE, false, false, true,
481        INFO_MIN_KEY_VALUE_PLACEHOLDER.get(), null, null, INFO_DESCRIPTION_BACKEND_DEBUG_MIN_KEY_VALUE.get());
482  }
483
484  private StringArgument newMaxKeyValueArg() throws ArgumentException
485  {
486    return new StringArgument(MAXKEYVALUE_NAME, 'K', MAXKEYVALUE, false, false, true,
487        INFO_MAX_KEY_VALUE_PLACEHOLDER.get(), null, null, INFO_DESCRIPTION_BACKEND_DEBUG_MAX_KEY_VALUE.get());
488  }
489
490  /**
491   * Parses the provided command-line arguments and makes the
492   * appropriate changes to the Directory Server configuration.
493   *
494   * @param args The command-line arguments provided to this program.
495   * @return The exit code from the configuration processing. A
496   * nonzero value indicates that there was some kind of
497   * problem during the configuration processing.
498   */
499  private int run(String[] args)
500  {
501    // Register global arguments and sub-commands.
502    try
503    {
504      initializeGlobalArguments();
505      initializeSubCommands();
506    }
507    catch (ArgumentException e)
508    {
509      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(e.getMessage()));
510      return 1;
511    }
512
513    try
514    {
515      parser.parseArguments(args);
516    }
517    catch (ArgumentException ae)
518    {
519      parser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
520      return 1;
521    }
522
523    if (parser.usageOrVersionDisplayed())
524    {
525      return 0;
526    }
527
528    if (parser.getSubCommand() == null)
529    {
530      parser.displayMessageAndUsageReference(err, ERR_BACKEND_DEBUG_MISSING_SUBCOMMAND.get());
531      return 1;
532    }
533
534    try
535    {
536      BuildVersion.checkVersionMismatch();
537    }
538    catch (InitializationException e)
539    {
540      printWrappedText(err, e.getMessageObject());
541      return 1;
542    }
543
544    // Perform the initial bootstrap of the Directory Server and process the configuration.
545    directoryServer = DirectoryServer.getInstance();
546    try
547    {
548      DirectoryServer.bootstrapClient();
549      DirectoryServer.initializeJMX();
550    }
551    catch (Exception e)
552    {
553      printWrappedText(err, ERR_SERVER_BOOTSTRAP_ERROR.get(getStartUpExceptionMessage(e)));
554      return 1;
555    }
556
557    try
558    {
559      directoryServer.initializeConfiguration(configClass.getValue(), configFile.getValue());
560    }
561    catch (Exception e)
562    {
563      printWrappedText(err, ERR_CANNOT_LOAD_CONFIG.get(getStartUpExceptionMessage(e)));
564      return 1;
565    }
566
567    try
568    {
569      directoryServer.initializeSchema();
570    }
571    catch (Exception e)
572    {
573      printWrappedText(err, ERR_CANNOT_LOAD_SCHEMA.get(getStartUpExceptionMessage(e)));
574      return 1;
575    }
576
577    try
578    {
579      CoreConfigManager coreConfigManager = new CoreConfigManager(directoryServer.getServerContext());
580      coreConfigManager.initializeCoreConfig();
581    }
582    catch (Exception e)
583    {
584      printWrappedText(err, ERR_CANNOT_INITIALIZE_CORE_CONFIG.get(getStartUpExceptionMessage(e)));
585      return 1;
586    }
587
588    try
589    {
590      directoryServer.initializeCryptoManager();
591    }
592    catch (Exception e)
593    {
594      printWrappedText(err, ERR_CANNOT_INITIALIZE_CRYPTO_MANAGER.get(getStartUpExceptionMessage(e)));
595      return 1;
596    }
597
598    SubCommand subCommand = parser.getSubCommand();
599    if (LIST_BACKENDS.equals(subCommand.getName()))
600    {
601      return listRootContainers();
602    }
603    BackendImpl backend = getBackendById(subCommand.getArgument(BACKENDID_NAME));
604    if (backend == null)
605    {
606      return 1;
607    }
608    RootContainer rootContainer = getAndLockRootContainer(backend);
609    if (rootContainer == null)
610    {
611      return 1;
612    }
613    try
614    {
615      switch (subCommand.getName())
616      {
617      case LIST_BASE_DNS:
618        return listBaseDNs(rootContainer);
619      case LIST_RAW_DBS:
620        return listRawDBs(rootContainer, subCommand.getArgument(USESIUNITS_NAME));
621      case LIST_INDEXES:
622        return listIndexes(rootContainer, backend, subCommand.getArgument(BASEDN_NAME));
623      case DUMP_RAW_DB:
624        return dumpTree(rootContainer, backend, subCommand, false);
625      case DUMP_INDEX:
626        return dumpTree(rootContainer, backend, subCommand, true);
627      case SHOW_INDEX_STATUS:
628        return showIndexStatus(rootContainer, backend, subCommand.getArgument(BASEDN_NAME));
629      default:
630        return 1;
631      }
632    }
633    catch (Exception e)
634    {
635      printWrappedText(err, ERR_BACKEND_TOOL_EXECUTING_COMMAND.get(subCommand.getName(),
636          StaticUtils.stackTraceToString(e)));
637      return 1;
638    }
639    finally
640    {
641      close(rootContainer);
642      releaseExclusiveLock(backend);
643    }
644  }
645
646  private String getStartUpExceptionMessage(Exception e)
647  {
648    if (e instanceof ConfigException || e instanceof InitializationException)
649    {
650      return e.getMessage();
651    }
652    return getExceptionMessage(e).toString();
653  }
654
655  private int dumpTree(RootContainer rc, BackendImpl backend, SubCommand subCommand, boolean isBackendTree)
656      throws ArgumentException, DirectoryException
657  {
658    Options options = Options.defaultOptions();
659    if (!setDumpTreeOptionArguments(subCommand, options))
660    {
661      return 1;
662    }
663    if (isBackendTree)
664    {
665      return dumpBackendTree(rc, backend, subCommand.getArgument(BASEDN_NAME), subCommand.getArgument(INDEXNAME_NAME),
666          options);
667    }
668    return dumpStorageTree(rc, backend, subCommand.getArgument(DBNAME_NAME), options);
669  }
670
671  private boolean setDumpTreeOptionArguments(SubCommand subCommand, Options options) throws ArgumentException
672  {
673    try
674    {
675      Argument arg = subCommand.getArgument(SINGLELINE_NAME);
676      if (arg != null && arg.isPresent())
677      {
678        options.set(DUMP_SINGLE_LINE, true);
679      }
680      if (subCommand.getArgument(STATSONLY_NAME).isPresent())
681      {
682        options.set(DUMP_STATS_ONLY, true);
683      }
684      arg = subCommand.getArgument(SKIPDECODE_NAME);
685      if (arg == null || arg.isPresent())
686      {
687        options.set(DUMP_DECODE_VALUE, false);
688      }
689      if (subCommand.getArgument(MINDATASIZE_NAME).isPresent())
690      {
691        options.set(DUMP_MIN_DATA_SIZE, subCommand.getArgument(MINDATASIZE_NAME).getIntValue());
692      }
693      if (subCommand.getArgument(MAXDATASIZE_NAME).isPresent())
694      {
695        options.set(DUMP_MAX_DATA_SIZE, subCommand.getArgument(MAXDATASIZE_NAME).getIntValue());
696      }
697
698      options.set(DUMP_MIN_KEY_VALUE, subCommand.getArgument(MINKEYVALUE_NAME));
699      if (subCommand.getArgument(MINHEXKEYVALUE_NAME).isPresent())
700      {
701        if (subCommand.getArgument(MINKEYVALUE_NAME).isPresent())
702        {
703          printWrappedText(err, ERR_BACKEND_TOOL_ONLY_ONE_MIN_KEY.get());
704          return false;
705        }
706        options.set(DUMP_MIN_KEY_VALUE_IS_HEX, true);
707        options.set(DUMP_MIN_KEY_VALUE, subCommand.getArgument(MINHEXKEYVALUE_NAME));
708      }
709
710      options.set(DUMP_MAX_KEY_VALUE, subCommand.getArgument(MAXKEYVALUE_NAME));
711      if (subCommand.getArgument(MAXHEXKEYVALUE_NAME).isPresent())
712      {
713        if (subCommand.getArgument(MAXKEYVALUE_NAME).isPresent())
714        {
715          printWrappedText(err, ERR_BACKEND_TOOL_ONLY_ONE_MAX_KEY.get());
716          return false;
717        }
718        options.set(DUMP_MAX_KEY_VALUE_IS_HEX, true);
719        options.set(DUMP_MAX_KEY_VALUE, subCommand.getArgument(MAXHEXKEYVALUE_NAME));
720      }
721      return true;
722    }
723    catch (ArgumentException ae)
724    {
725      printWrappedText(err, ERR_BACKEND_TOOL_PROCESSING_ARGUMENT.get(StaticUtils.stackTraceToString(ae)));
726      throw ae;
727    }
728  }
729
730  private int listRootContainers()
731  {
732    TableBuilder builder = new TableBuilder();
733
734    builder.appendHeading(INFO_LABEL_BACKEND_DEBUG_BACKEND_ID.get());
735    builder.appendHeading(INFO_LABEL_BACKEND_TOOL_STORAGE.get());
736
737    final Map<PluggableBackendCfg, BackendImpl> pluggableBackends = getPluggableBackends();
738    for (Map.Entry<PluggableBackendCfg, BackendImpl> backend : pluggableBackends.entrySet())
739    {
740      builder.startRow();
741      builder.appendCell(backend.getValue().getBackendID());
742      builder.appendCell(backend.getKey().getJavaClass());
743    }
744
745    builder.print(new TextTablePrinter(out));
746    out.format(INFO_LABEL_BACKEND_TOOL_TOTAL.get(pluggableBackends.size()).toString());
747
748    return 0;
749  }
750
751  private int listBaseDNs(RootContainer rc)
752  {
753    try
754    {
755      TableBuilder builder = new TableBuilder();
756      builder.appendHeading(INFO_LABEL_BACKEND_DEBUG_BASE_DN.get());
757      Collection<EntryContainer> entryContainers = rc.getEntryContainers();
758      for (EntryContainer ec : entryContainers)
759      {
760        builder.startRow();
761        builder.appendCell(ec.getBaseDN());
762      }
763
764      builder.print(new TextTablePrinter(out));
765      out.format(INFO_LABEL_BACKEND_TOOL_TOTAL.get(entryContainers.size()).toString());
766
767      return 0;
768    }
769    catch (StorageRuntimeException de)
770    {
771      printWrappedText(err, ERR_BACKEND_TOOL_ERROR_LISTING_BASE_DNS.get(stackTraceToSingleLineString(de)));
772      return 1;
773    }
774  }
775
776  private int listRawDBs(RootContainer rc, Argument useSIUnits)
777  {
778    try
779    {
780      TableBuilder builder = new TableBuilder();
781
782      builder.appendHeading(INFO_LABEL_BACKEND_TOOL_RAW_DB_NAME.get());
783      builder.appendHeading(INFO_LABEL_BACKEND_TOOL_TOTAL_KEYS.get());
784      builder.appendHeading(INFO_LABEL_BACKEND_TOOL_KEYS_SIZE.get());
785      builder.appendHeading(INFO_LABEL_BACKEND_TOOL_VALUES_SIZE.get());
786      builder.appendHeading(INFO_LABEL_BACKEND_TOOL_TOTAL_SIZES.get());
787
788      SortedSet<TreeName> treeNames = new TreeSet<>(rc.getStorage().listTrees());
789      for (TreeName tree: treeNames)
790      {
791        builder.startRow();
792        builder.appendCell(tree);
793        appendStorageTreeStats(builder, rc, new StorageTreeKeyValue(tree), useSIUnits.isPresent());
794      }
795
796      builder.print(new TextTablePrinter(out));
797      out.format(INFO_LABEL_BACKEND_TOOL_TOTAL.get(treeNames.size()).toString());
798
799      return 0;
800    }
801    catch (StorageRuntimeException de)
802    {
803      printWrappedText(err, ERR_BACKEND_TOOL_ERROR_LISTING_TREES.get(stackTraceToSingleLineString(de)));
804      return 1;
805    }
806  }
807
808  private void appendStorageTreeStats(TableBuilder builder, RootContainer rc, TreeKeyValue targetTree,
809      boolean useSIUnit)
810  {
811    Options options = Options.defaultOptions();
812    options.set(DUMP_STATS_ONLY, true);
813    try
814    {
815      options.set(DUMP_MIN_KEY_VALUE, newMinKeyValueArg());
816      options.set(DUMP_MAX_KEY_VALUE, newMaxKeyValueArg());
817      TreeStats treeStats = cursorTreeToDump(rc, targetTree, options);
818      builder.appendCell(treeStats.count);
819      builder.appendCell(appendKeyValueSize(treeStats.totalKeySize, useSIUnit));
820      builder.appendCell(appendKeyValueSize(treeStats.totalDataSize, useSIUnit));
821      builder.appendCell(appendKeyValueSize(treeStats.totalKeySize + treeStats.totalDataSize, useSIUnit));
822    }
823    catch (Exception e)
824    {
825      appendStatsNoData(builder, 3);
826    }
827  }
828
829  private String appendKeyValueSize(long size, boolean useSIUnit)
830  {
831    if (useSIUnit && size > SizeUnit.KILO_BYTES.getSize())
832    {
833      NumberFormat format = NumberFormat.getNumberInstance();
834      format.setMaximumFractionDigits(2);
835      SizeUnit unit = SizeUnit.getBestFitUnit(size);
836      return format.format(unit.fromBytes(size)) + " " + unit;
837    }
838    else
839    {
840      return String.valueOf(size);
841    }
842  }
843
844  private int listIndexes(RootContainer rc, BackendImpl backend, Argument baseDNArg) throws DirectoryException
845  {
846    DN base = null;
847    if (baseDNArg.isPresent())
848    {
849      base = getBaseDNFromArg(baseDNArg);
850    }
851
852    try
853    {
854      TableBuilder builder = new TableBuilder();
855      int count = 0;
856
857      builder.appendHeading(INFO_LABEL_BACKEND_DEBUG_INDEX_NAME.get());
858      builder.appendHeading(INFO_LABEL_BACKEND_TOOL_RAW_DB_NAME.get());
859      builder.appendHeading(INFO_LABEL_BACKEND_DEBUG_INDEX_TYPE.get());
860      builder.appendHeading(INFO_LABEL_BACKEND_DEBUG_RECORD_COUNT.get());
861
862      if (base != null)
863      {
864        EntryContainer ec = rc.getEntryContainer(base);
865        if (ec == null)
866        {
867          return printEntryContainerError(backend, base);
868        }
869        count = appendTreeRows(builder, ec);
870      }
871      else
872      {
873        for (EntryContainer ec : rc.getEntryContainers())
874        {
875          builder.startRow();
876          builder.appendCell("Base DN: " + ec.getBaseDN());
877          count += appendTreeRows(builder, ec);
878        }
879      }
880
881      builder.print(new TextTablePrinter(out));
882      out.format(INFO_LABEL_BACKEND_TOOL_TOTAL.get(count).toString());
883
884      return 0;
885    }
886    catch (StorageRuntimeException de)
887    {
888      printWrappedText(err, ERR_BACKEND_TOOL_ERROR_LISTING_TREES.get(stackTraceToSingleLineString(de)));
889      return 1;
890    }
891  }
892
893  private int printEntryContainerError(BackendImpl backend, DN base)
894  {
895    printWrappedText(err, ERR_BACKEND_DEBUG_NO_ENTRY_CONTAINERS_FOR_BASE_DN.get(base, backend.getBackendID()));
896    return 1;
897  }
898
899  private DN getBaseDNFromArg(Argument baseDNArg) throws DirectoryException
900  {
901    try
902    {
903      return DN.valueOf(baseDNArg.getValue());
904    }
905    catch (DirectoryException de)
906    {
907      printWrappedText(err, ERR_BACKEND_DEBUG_DECODE_BASE_DN.get(baseDNArg.getValue(), getExceptionMessage(de)));
908      throw de;
909    }
910  }
911
912  private RootContainer getAndLockRootContainer(BackendImpl backend)
913  {
914    try
915    {
916      String lockFile = LockFileManager.getBackendLockFileName(backend);
917      StringBuilder failureReason = new StringBuilder();
918      if (!LockFileManager.acquireExclusiveLock(lockFile, failureReason))
919      {
920        printWrappedText(err, ERR_BACKEND_DEBUG_CANNOT_LOCK_BACKEND.get(backend.getBackendID(), failureReason));
921        return null;
922      }
923    }
924    catch (Exception e)
925    {
926      printWrappedText(err, ERR_BACKEND_DEBUG_CANNOT_LOCK_BACKEND.get(backend.getBackendID(), StaticUtils
927          .getExceptionMessage(e)));
928      return null;
929    }
930
931    try
932    {
933      return backend.getReadOnlyRootContainer();
934    }
935    catch (Exception e)
936    {
937      printWrappedText(err, ERR_BACKEND_TOOL_ERROR_INITIALIZING_BACKEND.get(backend.getBackendID(),
938          stackTraceToSingleLineString(e)));
939      return null;
940    }
941  }
942
943  private int appendTreeRows(TableBuilder builder, EntryContainer ec)
944  {
945    int count = 0;
946    for (final Tree tree : ec.listTrees())
947    {
948      builder.startRow();
949      builder.appendCell(tree.getName().getIndexId());
950      builder.appendCell(tree.getName());
951      builder.appendCell(tree.getClass().getSimpleName());
952      builder.appendCell(getTreeRecordCount(ec, tree));
953      count++;
954    }
955    return count;
956  }
957
958  private long getTreeRecordCount(EntryContainer ec, final Tree tree)
959  {
960    try
961    {
962      return ec.getRootContainer().getStorage().read(new ReadOperation<Long>()
963      {
964        @Override
965        public Long run(ReadableTransaction txn) throws Exception
966        {
967          return tree.getRecordCount(txn);
968        }
969      });
970    }
971    catch (Exception e)
972    {
973      printWrappedText(err, ERR_BACKEND_TOOL_ERROR_READING_TREE.get(stackTraceToSingleLineString(e)));
974      return -1;
975    }
976  }
977
978  private void close(RootContainer rc)
979  {
980    try
981    {
982      rc.close();
983    }
984    catch (StorageRuntimeException ignored)
985    {
986      // Ignore.
987    }
988  }
989
990  private void releaseExclusiveLock(BackendImpl backend)
991  {
992    try
993    {
994      String lockFile = LockFileManager.getBackendLockFileName(backend);
995      StringBuilder failureReason = new StringBuilder();
996      if (!LockFileManager.releaseLock(lockFile, failureReason))
997      {
998        printWrappedText(err, WARN_BACKEND_DEBUG_CANNOT_UNLOCK_BACKEND.get(backend.getBackendID(), failureReason));
999      }
1000    }
1001    catch (Exception e)
1002    {
1003      printWrappedText(err, WARN_BACKEND_DEBUG_CANNOT_UNLOCK_BACKEND.get(backend.getBackendID(),
1004          StaticUtils.getExceptionMessage(e)));
1005    }
1006  }
1007
1008  private BackendImpl getBackendById(Argument backendIdArg)
1009  {
1010    final String backendID = backendIdArg.getValue();
1011    final Map<PluggableBackendCfg, BackendImpl> pluggableBackends = getPluggableBackends();
1012
1013    for (Map.Entry<PluggableBackendCfg, BackendImpl> backend : pluggableBackends.entrySet())
1014    {
1015      final BackendImpl b = backend.getValue();
1016      if (b.getBackendID().equalsIgnoreCase(backendID))
1017      {
1018        try
1019        {
1020          b.configureBackend(backend.getKey(), directoryServer.getServerContext());
1021          return b;
1022        }
1023        catch (ConfigException ce)
1024        {
1025          printWrappedText(err, ERR_BACKEND_TOOL_CANNOT_CONFIGURE_BACKEND.get(backendID, ce));
1026          return null;
1027        }
1028      }
1029    }
1030
1031    printWrappedText(err, ERR_BACKEND_DEBUG_NO_BACKENDS_FOR_ID.get(backendID));
1032    return null;
1033  }
1034
1035  private int showIndexStatus(RootContainer rc, BackendImpl backend, Argument baseDNArg) throws DirectoryException
1036  {
1037    DN base = getBaseDNFromArg(baseDNArg);
1038
1039    try
1040    {
1041      // Create a table of their properties.
1042      TableBuilder builder = new TableBuilder();
1043      int count = 0;
1044
1045      builder.appendHeading(INFO_LABEL_BACKEND_DEBUG_INDEX_NAME.get());
1046      builder.appendHeading(INFO_LABEL_BACKEND_TOOL_RAW_DB_NAME.get());
1047      builder.appendHeading(INFO_LABEL_BACKEND_DEBUG_INDEX_STATUS.get());
1048      builder.appendHeading(INFO_LABEL_BACKEND_DEBUG_RECORD_COUNT.get());
1049      builder.appendHeading(INFO_LABEL_BACKEND_TOOL_INDEX_UNDEFINED_RECORD_COUNT.get());
1050      builder.appendHeading(LocalizableMessage.raw("95%"));
1051      builder.appendHeading(LocalizableMessage.raw("90%"));
1052      builder.appendHeading(LocalizableMessage.raw("85%"));
1053
1054      EntryContainer ec = rc.getEntryContainer(base);
1055      if (ec == null)
1056      {
1057        return printEntryContainerError(backend, base);
1058      }
1059
1060      Map<Index, StringBuilder> undefinedKeys = new HashMap<>();
1061      for (AttributeIndex attrIndex : ec.getAttributeIndexes())
1062      {
1063        for (AttributeIndex.MatchingRuleIndex index : attrIndex.getNameToIndexes().values())
1064        {
1065          builder.startRow();
1066          builder.appendCell(index.getName().getIndexId());
1067          builder.appendCell(index.getName());
1068          builder.appendCell(index.isTrusted());
1069          if (index.isTrusted())
1070          {
1071            appendIndexStats(builder, ec, index, undefinedKeys);
1072          }
1073          else
1074          {
1075            appendStatsNoData(builder, 5);
1076          }
1077          count++;
1078        }
1079      }
1080
1081      for (VLVIndex vlvIndex : ec.getVLVIndexes())
1082      {
1083        builder.startRow();
1084        builder.appendCell(vlvIndex.getName().getIndexId());
1085        builder.appendCell(vlvIndex.getName());
1086        builder.appendCell(vlvIndex.isTrusted());
1087        builder.appendCell(getTreeRecordCount(ec, vlvIndex));
1088        appendStatsNoData(builder, 4);
1089        count++;
1090      }
1091
1092      builder.print(new TextTablePrinter(out));
1093      out.format(INFO_LABEL_BACKEND_TOOL_TOTAL.get(count).toString());
1094      for (Map.Entry<Index, StringBuilder> e : undefinedKeys.entrySet())
1095      {
1096        out.format(INFO_LABEL_BACKEND_TOOL_INDEX.get(e.getKey().getName()).toString());
1097        out.format(INFO_LABEL_BACKEND_TOOL_OVER_INDEX_LIMIT_KEYS.get(e.getValue()).toString());
1098      }
1099      return 0;
1100    }
1101    catch (StorageRuntimeException de)
1102    {
1103      printWrappedText(err, ERR_BACKEND_TOOL_ERROR_READING_TREE.get(stackTraceToSingleLineString(de)));
1104      return 1;
1105    }
1106  }
1107
1108  private void appendStatsNoData(TableBuilder builder, int columns)
1109  {
1110    while (columns > 0)
1111    {
1112      builder.appendCell("-");
1113      columns--;
1114    }
1115  }
1116
1117  private void appendIndexStats(final TableBuilder builder, EntryContainer ec, final Index index,
1118      final Map<Index, StringBuilder> undefinedKeys)
1119  {
1120    final long entryLimit = index.getIndexEntryLimit();
1121
1122    try
1123    {
1124      ec.getRootContainer().getStorage().read(new ReadOperation<Void>()
1125      {
1126        @Override
1127        public Void run(ReadableTransaction txn) throws Exception
1128        {
1129          long eighty = 0;
1130          long ninety = 0;
1131          long ninetyFive = 0;
1132          long undefined = 0;
1133          long count = 0;
1134          BackendTreeKeyValue keyDecoder = new BackendTreeKeyValue(index);
1135          try (Cursor<ByteString, EntryIDSet> cursor = index.openCursor(txn))
1136          {
1137            while (cursor.next())
1138            {
1139              count++;
1140              EntryIDSet entryIDSet;
1141              try
1142              {
1143                entryIDSet = cursor.getValue();
1144              }
1145              catch (Exception e)
1146              {
1147                continue;
1148              }
1149
1150              if (entryIDSet.isDefined())
1151              {
1152                if (entryIDSet.size() >= entryLimit * 0.8)
1153                {
1154                  if (entryIDSet.size() >= entryLimit * 0.95)
1155                  {
1156                    ninetyFive++;
1157                  }
1158                  else if (entryIDSet.size() >= entryLimit * 0.9)
1159                  {
1160                    ninety++;
1161                  }
1162                  else
1163                  {
1164                    eighty++;
1165                  }
1166                }
1167              }
1168              else
1169              {
1170                undefined++;
1171                StringBuilder keyList = undefinedKeys.get(index);
1172                if (keyList == null)
1173                {
1174                  keyList = new StringBuilder();
1175                  undefinedKeys.put(index, keyList);
1176                }
1177                else
1178                {
1179                  keyList.append(" ");
1180                }
1181                keyList.append("[").append(keyDecoder.keyDecoder(cursor.getKey())).append("]");
1182              }
1183            }
1184          }
1185          builder.appendCell(count);
1186          builder.appendCell(undefined);
1187          builder.appendCell(ninetyFive);
1188          builder.appendCell(ninety);
1189          builder.appendCell(eighty);
1190          return null;
1191        }
1192      });
1193    }
1194    catch (Exception e)
1195    {
1196      appendStatsNoData(builder, 5);
1197      printWrappedText(err, ERR_BACKEND_TOOL_ERROR_READING_TREE.get(index.getName()));
1198    }
1199  }
1200
1201  private int dumpStorageTree(RootContainer rc, BackendImpl backend, Argument treeNameArg, Options options)
1202  {
1203    TreeName targetTree = getStorageTreeName(treeNameArg, rc);
1204    if (targetTree == null)
1205    {
1206      printWrappedText(err,
1207          ERR_BACKEND_TOOL_NO_TREE_FOR_NAME_IN_STORAGE.get(treeNameArg.getValue(), backend.getBackendID()));
1208      return 1;
1209    }
1210
1211    try
1212    {
1213      dumpActualTree(rc, new StorageTreeKeyValue(targetTree), options);
1214      return 0;
1215    }
1216    catch (Exception e)
1217    {
1218      printWrappedText(err, ERR_BACKEND_TOOL_ERROR_READING_TREE.get(stackTraceToSingleLineString(e)));
1219      return 1;
1220    }
1221  }
1222
1223  private TreeName getStorageTreeName(Argument treeNameArg, RootContainer rc)
1224  {
1225    for (TreeName tree : rc.getStorage().listTrees())
1226    {
1227      if (treeNameArg.getValue().equals(tree.toString()))
1228      {
1229        return tree;
1230      }
1231    }
1232    return null;
1233  }
1234
1235  private int dumpBackendTree(RootContainer rc, BackendImpl backend, Argument baseDNArg, Argument treeNameArg,
1236      Options options) throws DirectoryException
1237  {
1238    DN base = getBaseDNFromArg(baseDNArg);
1239
1240    EntryContainer ec = rc.getEntryContainer(base);
1241    if (ec == null)
1242    {
1243      return printEntryContainerError(backend, base);
1244    }
1245
1246    Tree targetTree = getBackendTree(treeNameArg, ec);
1247    if (targetTree == null)
1248    {
1249      printWrappedText(err,
1250          ERR_BACKEND_TOOL_NO_TREE_FOR_NAME.get(treeNameArg.getValue(), base, backend.getBackendID()));
1251      return 1;
1252    }
1253
1254    try
1255    {
1256      dumpActualTree(rc, new BackendTreeKeyValue(targetTree), options);
1257      return 0;
1258    }
1259    catch (Exception e)
1260    {
1261      printWrappedText(err, ERR_BACKEND_TOOL_ERROR_READING_TREE.get(stackTraceToSingleLineString(e)));
1262      return 1;
1263    }
1264  }
1265
1266  private Tree getBackendTree(Argument treeNameArg, EntryContainer ec)
1267  {
1268    for (Tree tree : ec.listTrees())
1269    {
1270      if (treeNameArg.getValue().contains(tree.getName().getIndexId())
1271          || treeNameArg.getValue().equals(tree.getName().toString()))
1272      {
1273        return tree;
1274      }
1275    }
1276    return null;
1277  }
1278
1279  private void dumpActualTree(RootContainer rc, final TreeKeyValue target, final Options options) throws Exception
1280  {
1281    TreeStats treeStats =  cursorTreeToDump(rc, target, options);
1282    out.format(INFO_LABEL_BACKEND_TOOL_TOTAL_RECORDS.get(treeStats.count).toString());
1283    if (treeStats.count > 0)
1284    {
1285      out.format(INFO_LABEL_BACKEND_TOOL_TOTAL_KEY_SIZE_AND_AVG.get(
1286          treeStats.totalKeySize, treeStats.totalKeySize / treeStats.count).toString());
1287      out.format(INFO_LABEL_BACKEND_TOOL_TOTAL_DATA_SIZE_AND_AVG.get(
1288          treeStats.totalDataSize, treeStats.totalDataSize / treeStats.count).toString());
1289    }
1290  }
1291
1292  private TreeStats cursorTreeToDump(RootContainer rc, final TreeKeyValue target, final Options options)
1293      throws Exception
1294  {
1295    return rc.getStorage().read(new ReadOperation<TreeStats>()
1296      {
1297        @Override
1298        public TreeStats run(ReadableTransaction txn) throws Exception
1299        {
1300          long count = 0;
1301          long totalKeySize = 0;
1302          long totalDataSize = 0;
1303          try (final Cursor<ByteString, ByteString> cursor = txn.openCursor(target.getTreeName()))
1304          {
1305            ByteString key;
1306            ByteString maxKey = null;
1307            ByteString value;
1308
1309            if (options.get(DUMP_MIN_KEY_VALUE).isPresent())
1310            {
1311              key = getMinOrMaxKey(options, DUMP_MIN_KEY_VALUE, DUMP_MIN_KEY_VALUE_IS_HEX);
1312              if (!cursor.positionToKeyOrNext(key))
1313              {
1314                return new TreeStats(0, 0, 0);
1315              }
1316            }
1317            else
1318            {
1319              if (!cursor.next())
1320              {
1321                return new TreeStats(0, 0, 0);
1322              }
1323            }
1324
1325            if (options.get(DUMP_MAX_KEY_VALUE).isPresent())
1326            {
1327              maxKey = getMinOrMaxKey(options, DUMP_MAX_KEY_VALUE, DUMP_MAX_KEY_VALUE_IS_HEX);
1328            }
1329
1330            do
1331            {
1332              key = cursor.getKey();
1333              if (maxKey != null && key.compareTo(maxKey) > 0)
1334              {
1335                break;
1336              }
1337              value = cursor.getValue();
1338              long valueLen = value.length();
1339              if (options.get(DUMP_MIN_DATA_SIZE) <= valueLen && valueLen <= options.get(DUMP_MAX_DATA_SIZE))
1340              {
1341                count++;
1342                int keyLen = key.length();
1343                totalKeySize += keyLen;
1344                totalDataSize += valueLen;
1345                if (!options.get(DUMP_STATS_ONLY))
1346                {
1347                  if (options.get(DUMP_DECODE_VALUE))
1348                  {
1349                    String k = target.keyDecoder(key);
1350                    String v = target.valueDecoder(value);
1351                    out.format(INFO_LABEL_BACKEND_TOOL_KEY_FORMAT.get(keyLen) + " %s%n"
1352                        + INFO_LABEL_BACKEND_TOOL_VALUE_FORMAT.get(valueLen) + " %s%n", k, v);
1353                  }
1354                  else
1355                  {
1356                    hexDumpRecord(key, value, out, options);
1357                  }
1358                }
1359              }
1360            }
1361            while (cursor.next());
1362          }
1363          catch (Exception e)
1364          {
1365            out.format(ERR_BACKEND_TOOL_CURSOR_AT_KEY_NUMBER.get(count, e.getCause()).toString());
1366            e.printStackTrace(out);
1367            out.format("%n");
1368            throw e;
1369          }
1370          return new TreeStats(count, totalKeySize, totalDataSize);
1371        }
1372
1373      private ByteString getMinOrMaxKey(Options options, Option<Argument> keyOpt, Option<Boolean> isHexKey)
1374      {
1375        ByteString key;
1376        if (options.get(isHexKey))
1377        {
1378          key = ByteString.valueOfHex(options.get(keyOpt).getValue());
1379        }
1380        else
1381        {
1382          key = target.getTreeKey(options.get(keyOpt).getValue());
1383        }
1384        return key;
1385      }
1386    });
1387  }
1388
1389  final void hexDumpRecord(ByteString key, ByteString value, PrintStream out, Options options)
1390  {
1391    if (options.get(DUMP_SINGLE_LINE))
1392    {
1393      out.format(INFO_LABEL_BACKEND_TOOL_KEY_FORMAT.get(key.length()) + " ");
1394      toHexDumpSingleLine(out, key);
1395      out.format(INFO_LABEL_BACKEND_TOOL_VALUE_FORMAT.get(value.length()) + " ");
1396      toHexDumpSingleLine(out, value);
1397    }
1398    else
1399    {
1400      out.format(INFO_LABEL_BACKEND_TOOL_KEY_FORMAT.get(key.length()) + "%n");
1401      toHexDumpWithAsciiCompact(key, options.get(DUMP_INDENT), out);
1402      out.format(INFO_LABEL_BACKEND_TOOL_VALUE_FORMAT.get(value.length()) + "%n");
1403      toHexDumpWithAsciiCompact(value, options.get(DUMP_INDENT), out);
1404    }
1405  }
1406
1407  final void toHexDumpSingleLine(PrintStream out, ByteString data)
1408  {
1409    for (int i = 0; i < data.length(); i++)
1410    {
1411      out.format("%s", StaticUtils.byteToHex(data.byteAt(i)));
1412    }
1413    out.format("%n");
1414  }
1415
1416  final void toHexDumpWithAsciiCompact(ByteString data, int indent, PrintStream out)
1417  {
1418    StringBuilder hexDump = new StringBuilder();
1419    StringBuilder indentBuilder = new StringBuilder();
1420    StringBuilder asciiDump = new StringBuilder();
1421    for (int i = 0; i < indent; i++)
1422    {
1423      indentBuilder.append(' ');
1424    }
1425    int pos = 0;
1426    while (pos < data.length())
1427    {
1428      byte val = data.byteAt(pos);
1429      hexDump.append(StaticUtils.byteToHex(val));
1430      hexDump.append(' ');
1431      asciiDump.append(val >= ' ' ? (char)val : ".");
1432      pos++;
1433      if (pos % 16 == 0)
1434      {
1435        out.format(HEXDUMP_LINE_FORMAT, indentBuilder.toString(), hexDump.toString(), asciiDump.toString());
1436        hexDump.setLength(0);
1437        asciiDump.setLength(0);
1438      }
1439    }
1440    while (pos % 16 != 0)
1441    {
1442      hexDump.append("   ");
1443      pos++;
1444    }
1445    out.format(HEXDUMP_LINE_FORMAT, indentBuilder.toString(), hexDump.toString(), asciiDump.toString());
1446  }
1447
1448  private static Map<PluggableBackendCfg, BackendImpl> getPluggableBackends()
1449  {
1450    ArrayList<Backend> backendList = new ArrayList<>();
1451    ArrayList<BackendCfg> entryList = new ArrayList<>();
1452    ArrayList<List<DN>> dnList = new ArrayList<>();
1453    BackendToolUtils.getBackends(backendList, entryList, dnList);
1454
1455    final Map<PluggableBackendCfg, BackendImpl> pluggableBackends = new LinkedHashMap<>();
1456    for (int i = 0; i < backendList.size(); i++)
1457    {
1458      Backend<?> backend = backendList.get(i);
1459      if (backend instanceof BackendImpl)
1460      {
1461        pluggableBackends.put((PluggableBackendCfg) entryList.get(i), (BackendImpl) backend);
1462      }
1463    }
1464    return pluggableBackends;
1465  }
1466}