001/* 002 * Copyright 2010-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2015-2019 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk.unboundidds.examples; 022 023 024 025import java.io.BufferedOutputStream; 026import java.io.File; 027import java.io.FileOutputStream; 028import java.io.IOException; 029import java.io.OutputStream; 030import java.io.PrintStream; 031import java.util.LinkedHashMap; 032import java.util.List; 033import java.util.concurrent.atomic.AtomicLong; 034 035import com.unboundid.asn1.ASN1OctetString; 036import com.unboundid.ldap.sdk.ExtendedResult; 037import com.unboundid.ldap.sdk.LDAPConnection; 038import com.unboundid.ldap.sdk.LDAPConnectionOptions; 039import com.unboundid.ldap.sdk.LDAPException; 040import com.unboundid.ldap.sdk.IntermediateResponse; 041import com.unboundid.ldap.sdk.IntermediateResponseListener; 042import com.unboundid.ldap.sdk.ResultCode; 043import com.unboundid.ldap.sdk.SearchScope; 044import com.unboundid.ldap.sdk.Version; 045import com.unboundid.ldap.sdk.unboundidds.extensions. 046 StreamDirectoryValuesExtendedRequest; 047import com.unboundid.ldap.sdk.unboundidds.extensions. 048 StreamDirectoryValuesIntermediateResponse; 049import com.unboundid.util.LDAPCommandLineTool; 050import com.unboundid.util.StaticUtils; 051import com.unboundid.util.ThreadSafety; 052import com.unboundid.util.ThreadSafetyLevel; 053import com.unboundid.util.args.ArgumentException; 054import com.unboundid.util.args.ArgumentParser; 055import com.unboundid.util.args.DNArgument; 056import com.unboundid.util.args.FileArgument; 057 058 059 060/** 061 * This class provides a utility that uses the stream directory values extended 062 * operation in order to obtain a listing of all entry DNs below a specified 063 * base DN in the Directory Server. 064 * <BR> 065 * <BLOCKQUOTE> 066 * <B>NOTE:</B> This class, and other classes within the 067 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 068 * supported for use against Ping Identity, UnboundID, and 069 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 070 * for proprietary functionality or for external specifications that are not 071 * considered stable or mature enough to be guaranteed to work in an 072 * interoperable way with other types of LDAP servers. 073 * </BLOCKQUOTE> 074 * <BR> 075 * The APIs demonstrated by this example include: 076 * <UL> 077 * <LI>The use of the stream directory values extended operation.</LI> 078 * <LI>Intermediate response processing.</LI> 079 * <LI>The LDAP command-line tool API.</LI> 080 * <LI>Argument parsing.</LI> 081 * </UL> 082 */ 083@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 084public final class DumpDNs 085 extends LDAPCommandLineTool 086 implements IntermediateResponseListener 087{ 088 /** 089 * The serial version UID for this serializable class. 090 */ 091 private static final long serialVersionUID = 774432759537092866L; 092 093 094 095 // The argument used to obtain the base DN. 096 private DNArgument baseDN; 097 098 // The argument used to obtain the output file. 099 private FileArgument outputFile; 100 101 // The number of DNs dumped. 102 private final AtomicLong dnsWritten; 103 104 // The print stream that will be used to output the DNs. 105 private PrintStream outputStream; 106 107 108 109 /** 110 * Parse the provided command line arguments and perform the appropriate 111 * processing. 112 * 113 * @param args The command line arguments provided to this program. 114 */ 115 public static void main(final String[] args) 116 { 117 final ResultCode resultCode = main(args, System.out, System.err); 118 if (resultCode != ResultCode.SUCCESS) 119 { 120 System.exit(resultCode.intValue()); 121 } 122 } 123 124 125 126 /** 127 * Parse the provided command line arguments and perform the appropriate 128 * processing. 129 * 130 * @param args The command line arguments provided to this program. 131 * @param outStream The output stream to which standard out should be 132 * written. It may be {@code null} if output should be 133 * suppressed. 134 * @param errStream The output stream to which standard error should be 135 * written. It may be {@code null} if error messages 136 * should be suppressed. 137 * 138 * @return A result code indicating whether the processing was successful. 139 */ 140 public static ResultCode main(final String[] args, 141 final OutputStream outStream, 142 final OutputStream errStream) 143 { 144 final DumpDNs tool = new DumpDNs(outStream, errStream); 145 return tool.runTool(args); 146 } 147 148 149 150 /** 151 * Creates a new instance of this tool. 152 * 153 * @param outStream The output stream to which standard out should be 154 * written. It may be {@code null} if output should be 155 * suppressed. 156 * @param errStream The output stream to which standard error should be 157 * written. It may be {@code null} if error messages 158 * should be suppressed. 159 */ 160 public DumpDNs(final OutputStream outStream, final OutputStream errStream) 161 { 162 super(outStream, errStream); 163 164 baseDN = null; 165 outputFile = null; 166 outputStream = null; 167 dnsWritten = new AtomicLong(0L); 168 } 169 170 171 172 /** 173 * Retrieves the name of this tool. It should be the name of the command used 174 * to invoke this tool. 175 * 176 * @return The name for this tool. 177 */ 178 @Override() 179 public String getToolName() 180 { 181 return "dump-dns"; 182 } 183 184 185 186 /** 187 * Retrieves a human-readable description for this tool. 188 * 189 * @return A human-readable description for this tool. 190 */ 191 @Override() 192 public String getToolDescription() 193 { 194 return "Obtain a listing of all of the DNs for all entries below a " + 195 "specified base DN in the Directory Server."; 196 } 197 198 199 200 /** 201 * Retrieves the version string for this tool. 202 * 203 * @return The version string for this tool. 204 */ 205 @Override() 206 public String getToolVersion() 207 { 208 return Version.NUMERIC_VERSION_STRING; 209 } 210 211 212 213 /** 214 * Indicates whether this tool should provide support for an interactive mode, 215 * in which the tool offers a mode in which the arguments can be provided in 216 * a text-driven menu rather than requiring them to be given on the command 217 * line. If interactive mode is supported, it may be invoked using the 218 * "--interactive" argument. Alternately, if interactive mode is supported 219 * and {@link #defaultsToInteractiveMode()} returns {@code true}, then 220 * interactive mode may be invoked by simply launching the tool without any 221 * arguments. 222 * 223 * @return {@code true} if this tool supports interactive mode, or 224 * {@code false} if not. 225 */ 226 @Override() 227 public boolean supportsInteractiveMode() 228 { 229 return true; 230 } 231 232 233 234 /** 235 * Indicates whether this tool defaults to launching in interactive mode if 236 * the tool is invoked without any command-line arguments. This will only be 237 * used if {@link #supportsInteractiveMode()} returns {@code true}. 238 * 239 * @return {@code true} if this tool defaults to using interactive mode if 240 * launched without any command-line arguments, or {@code false} if 241 * not. 242 */ 243 @Override() 244 public boolean defaultsToInteractiveMode() 245 { 246 return true; 247 } 248 249 250 251 /** 252 * Indicates whether this tool should default to interactively prompting for 253 * the bind password if a password is required but no argument was provided 254 * to indicate how to get the password. 255 * 256 * @return {@code true} if this tool should default to interactively 257 * prompting for the bind password, or {@code false} if not. 258 */ 259 @Override() 260 protected boolean defaultToPromptForBindPassword() 261 { 262 return true; 263 } 264 265 266 267 /** 268 * Indicates whether this tool supports the use of a properties file for 269 * specifying default values for arguments that aren't specified on the 270 * command line. 271 * 272 * @return {@code true} if this tool supports the use of a properties file 273 * for specifying default values for arguments that aren't specified 274 * on the command line, or {@code false} if not. 275 */ 276 @Override() 277 public boolean supportsPropertiesFile() 278 { 279 return true; 280 } 281 282 283 284 /** 285 * Indicates whether the LDAP-specific arguments should include alternate 286 * versions of all long identifiers that consist of multiple words so that 287 * they are available in both camelCase and dash-separated versions. 288 * 289 * @return {@code true} if this tool should provide multiple versions of 290 * long identifiers for LDAP-specific arguments, or {@code false} if 291 * not. 292 */ 293 @Override() 294 protected boolean includeAlternateLongIdentifiers() 295 { 296 return true; 297 } 298 299 300 301 /** 302 * Indicates whether this tool should provide a command-line argument that 303 * allows for low-level SSL debugging. If this returns {@code true}, then an 304 * "--enableSSLDebugging}" argument will be added that sets the 305 * "javax.net.debug" system property to "all" before attempting any 306 * communication. 307 * 308 * @return {@code true} if this tool should offer an "--enableSSLDebugging" 309 * argument, or {@code false} if not. 310 */ 311 @Override() 312 protected boolean supportsSSLDebugging() 313 { 314 return true; 315 } 316 317 318 319 /** 320 * Adds the arguments needed by this command-line tool to the provided 321 * argument parser which are not related to connecting or authenticating to 322 * the directory server. 323 * 324 * @param parser The argument parser to which the arguments should be added. 325 * 326 * @throws ArgumentException If a problem occurs while adding the arguments. 327 */ 328 @Override() 329 public void addNonLDAPArguments(final ArgumentParser parser) 330 throws ArgumentException 331 { 332 baseDN = new DNArgument('b', "baseDN", true, 1, "{dn}", 333 "The base DN below which to dump the DNs of all entries in the " + 334 "Directory Server."); 335 baseDN.addLongIdentifier("base-dn", true); 336 parser.addArgument(baseDN); 337 338 outputFile = new FileArgument('f', "outputFile", false, 1, "{path}", 339 "The path of the output file to which the entry DNs will be " + 340 "written. If this is not provided, then entry DNs will be " + 341 "written to standard output.", false, true, true, false); 342 outputFile.addLongIdentifier("output-file", true); 343 parser.addArgument(outputFile); 344 } 345 346 347 348 /** 349 * Retrieves the connection options that should be used for connections that 350 * are created with this command line tool. Subclasses may override this 351 * method to use a custom set of connection options. 352 * 353 * @return The connection options that should be used for connections that 354 * are created with this command line tool. 355 */ 356 @Override() 357 public LDAPConnectionOptions getConnectionOptions() 358 { 359 final LDAPConnectionOptions options = new LDAPConnectionOptions(); 360 361 options.setUseSynchronousMode(true); 362 options.setResponseTimeoutMillis(0L); 363 364 return options; 365 } 366 367 368 369 /** 370 * Performs the core set of processing for this tool. 371 * 372 * @return A result code that indicates whether the processing completed 373 * successfully. 374 */ 375 @Override() 376 public ResultCode doToolProcessing() 377 { 378 // Create the writer that will be used to write the DNs. 379 final File f = outputFile.getValue(); 380 if (f == null) 381 { 382 outputStream = getOut(); 383 } 384 else 385 { 386 try 387 { 388 outputStream = 389 new PrintStream(new BufferedOutputStream(new FileOutputStream(f))); 390 } 391 catch (final IOException ioe) 392 { 393 err("Unable to open output file '", f.getAbsolutePath(), 394 " for writing: ", StaticUtils.getExceptionMessage(ioe)); 395 return ResultCode.LOCAL_ERROR; 396 } 397 } 398 399 400 // Obtain a connection to the Directory Server. 401 final LDAPConnection conn; 402 try 403 { 404 conn = getConnection(); 405 } 406 catch (final LDAPException le) 407 { 408 err("Unable to obtain a connection to the Directory Server: ", 409 le.getExceptionMessage()); 410 return le.getResultCode(); 411 } 412 413 414 // Create the extended request. Register this class as an intermediate 415 // response listener, and indicate that we don't want any response time 416 // limit. 417 final StreamDirectoryValuesExtendedRequest streamValuesRequest = 418 new StreamDirectoryValuesExtendedRequest(baseDN.getStringValue(), 419 SearchScope.SUB, false, null, 1000); 420 streamValuesRequest.setIntermediateResponseListener(this); 421 streamValuesRequest.setResponseTimeoutMillis(0L); 422 423 424 // Send the extended request to the server and get the result. 425 try 426 { 427 final ExtendedResult streamValuesResult = 428 conn.processExtendedOperation(streamValuesRequest); 429 err("Processing completed. ", dnsWritten.get(), " DNs written."); 430 return streamValuesResult.getResultCode(); 431 } 432 catch (final LDAPException le) 433 { 434 err("Unable to send the stream directory values extended request to " + 435 "the Directory Server: ", le.getExceptionMessage()); 436 return le.getResultCode(); 437 } 438 finally 439 { 440 if (f != null) 441 { 442 outputStream.close(); 443 } 444 445 conn.close(); 446 } 447 } 448 449 450 451 /** 452 * Retrieves a set of information that may be used to generate example usage 453 * information. Each element in the returned map should consist of a map 454 * between an example set of arguments and a string that describes the 455 * behavior of the tool when invoked with that set of arguments. 456 * 457 * @return A set of information that may be used to generate example usage 458 * information. It may be {@code null} or empty if no example usage 459 * information is available. 460 */ 461 @Override() 462 public LinkedHashMap<String[],String> getExampleUsages() 463 { 464 final LinkedHashMap<String[],String> exampleMap = 465 new LinkedHashMap<>(StaticUtils.computeMapCapacity(1)); 466 467 final String[] args = 468 { 469 "--hostname", "server.example.com", 470 "--port", "389", 471 "--bindDN", "uid=admin,dc=example,dc=com", 472 "--bindPassword", "password", 473 "--baseDN", "dc=example,dc=com", 474 "--outputFile", "example-dns.txt", 475 }; 476 exampleMap.put(args, 477 "Dump all entry DNs at or below 'dc=example,dc=com' to the file " + 478 "'example-dns.txt'"); 479 480 return exampleMap; 481 } 482 483 484 485 /** 486 * Indicates that the provided intermediate response has been returned by the 487 * server and may be processed by this intermediate response listener. In 488 * this case, it will 489 * 490 * @param intermediateResponse The intermediate response that has been 491 * returned by the server. 492 */ 493 @Override() 494 public void intermediateResponseReturned( 495 final IntermediateResponse intermediateResponse) 496 { 497 // Try to parse the intermediate response as a stream directory values 498 // intermediate response. 499 final StreamDirectoryValuesIntermediateResponse streamValuesIR; 500 try 501 { 502 streamValuesIR = 503 new StreamDirectoryValuesIntermediateResponse(intermediateResponse); 504 } 505 catch (final LDAPException le) 506 { 507 err("Unable to parse an intermediate response message as a stream " + 508 "directory values intermediate response: ", 509 le.getExceptionMessage()); 510 return; 511 } 512 513 final String diagnosticMessage = streamValuesIR.getDiagnosticMessage(); 514 if ((diagnosticMessage != null) && (! diagnosticMessage.isEmpty())) 515 { 516 err(diagnosticMessage); 517 } 518 519 520 final List<ASN1OctetString> values = streamValuesIR.getValues(); 521 if ((values != null) && (! values.isEmpty())) 522 { 523 for (final ASN1OctetString s : values) 524 { 525 outputStream.println(s.toString()); 526 } 527 528 final long updatedCount = dnsWritten.addAndGet(values.size()); 529 if (outputFile.isPresent()) 530 { 531 err(updatedCount, " DNs written."); 532 } 533 } 534 } 535}