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