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