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