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 * Adds the arguments needed by this command-line tool to the provided 303 * argument parser which are not related to connecting or authenticating to 304 * the directory server. 305 * 306 * @param parser The argument parser to which the arguments should be added. 307 * 308 * @throws ArgumentException If a problem occurs while adding the arguments. 309 */ 310 @Override() 311 public void addNonLDAPArguments(final ArgumentParser parser) 312 throws ArgumentException 313 { 314 baseDN = new DNArgument('b', "baseDN", true, 1, "{dn}", 315 "The base DN below which to dump the DNs of all entries in the " + 316 "Directory Server."); 317 baseDN.addLongIdentifier("base-dn", true); 318 parser.addArgument(baseDN); 319 320 outputFile = new FileArgument('f', "outputFile", false, 1, "{path}", 321 "The path of the output file to which the entry DNs will be " + 322 "written. If this is not provided, then entry DNs will be " + 323 "written to standard output.", false, true, true, false); 324 outputFile.addLongIdentifier("output-file", true); 325 parser.addArgument(outputFile); 326 } 327 328 329 330 /** 331 * Retrieves the connection options that should be used for connections that 332 * are created with this command line tool. Subclasses may override this 333 * method to use a custom set of connection options. 334 * 335 * @return The connection options that should be used for connections that 336 * are created with this command line tool. 337 */ 338 @Override() 339 public LDAPConnectionOptions getConnectionOptions() 340 { 341 final LDAPConnectionOptions options = new LDAPConnectionOptions(); 342 343 options.setUseSynchronousMode(true); 344 options.setResponseTimeoutMillis(0L); 345 346 return options; 347 } 348 349 350 351 /** 352 * Performs the core set of processing for this tool. 353 * 354 * @return A result code that indicates whether the processing completed 355 * successfully. 356 */ 357 @Override() 358 public ResultCode doToolProcessing() 359 { 360 // Create the writer that will be used to write the DNs. 361 final File f = outputFile.getValue(); 362 if (f == null) 363 { 364 outputStream = getOut(); 365 } 366 else 367 { 368 try 369 { 370 outputStream = 371 new PrintStream(new BufferedOutputStream(new FileOutputStream(f))); 372 } 373 catch (final IOException ioe) 374 { 375 err("Unable to open output file '", f.getAbsolutePath(), 376 " for writing: ", StaticUtils.getExceptionMessage(ioe)); 377 return ResultCode.LOCAL_ERROR; 378 } 379 } 380 381 382 // Obtain a connection to the Directory Server. 383 final LDAPConnection conn; 384 try 385 { 386 conn = getConnection(); 387 } 388 catch (final LDAPException le) 389 { 390 err("Unable to obtain a connection to the Directory Server: ", 391 le.getExceptionMessage()); 392 return le.getResultCode(); 393 } 394 395 396 // Create the extended request. Register this class as an intermediate 397 // response listener, and indicate that we don't want any response time 398 // limit. 399 final StreamDirectoryValuesExtendedRequest streamValuesRequest = 400 new StreamDirectoryValuesExtendedRequest(baseDN.getStringValue(), 401 SearchScope.SUB, false, null, 1000); 402 streamValuesRequest.setIntermediateResponseListener(this); 403 streamValuesRequest.setResponseTimeoutMillis(0L); 404 405 406 // Send the extended request to the server and get the result. 407 try 408 { 409 final ExtendedResult streamValuesResult = 410 conn.processExtendedOperation(streamValuesRequest); 411 err("Processing completed. ", dnsWritten.get(), " DNs written."); 412 return streamValuesResult.getResultCode(); 413 } 414 catch (final LDAPException le) 415 { 416 err("Unable to send the stream directory values extended request to " + 417 "the Directory Server: ", le.getExceptionMessage()); 418 return le.getResultCode(); 419 } 420 finally 421 { 422 if (f != null) 423 { 424 outputStream.close(); 425 } 426 427 conn.close(); 428 } 429 } 430 431 432 433 /** 434 * Retrieves a set of information that may be used to generate example usage 435 * information. Each element in the returned map should consist of a map 436 * between an example set of arguments and a string that describes the 437 * behavior of the tool when invoked with that set of arguments. 438 * 439 * @return A set of information that may be used to generate example usage 440 * information. It may be {@code null} or empty if no example usage 441 * information is available. 442 */ 443 @Override() 444 public LinkedHashMap<String[],String> getExampleUsages() 445 { 446 final LinkedHashMap<String[],String> exampleMap = 447 new LinkedHashMap<>(StaticUtils.computeMapCapacity(1)); 448 449 final String[] args = 450 { 451 "--hostname", "server.example.com", 452 "--port", "389", 453 "--bindDN", "uid=admin,dc=example,dc=com", 454 "--bindPassword", "password", 455 "--baseDN", "dc=example,dc=com", 456 "--outputFile", "example-dns.txt", 457 }; 458 exampleMap.put(args, 459 "Dump all entry DNs at or below 'dc=example,dc=com' to the file " + 460 "'example-dns.txt'"); 461 462 return exampleMap; 463 } 464 465 466 467 /** 468 * Indicates that the provided intermediate response has been returned by the 469 * server and may be processed by this intermediate response listener. In 470 * this case, it will 471 * 472 * @param intermediateResponse The intermediate response that has been 473 * returned by the server. 474 */ 475 @Override() 476 public void intermediateResponseReturned( 477 final IntermediateResponse intermediateResponse) 478 { 479 // Try to parse the intermediate response as a stream directory values 480 // intermediate response. 481 final StreamDirectoryValuesIntermediateResponse streamValuesIR; 482 try 483 { 484 streamValuesIR = 485 new StreamDirectoryValuesIntermediateResponse(intermediateResponse); 486 } 487 catch (final LDAPException le) 488 { 489 err("Unable to parse an intermediate response message as a stream " + 490 "directory values intermediate response: ", 491 le.getExceptionMessage()); 492 return; 493 } 494 495 final String diagnosticMessage = streamValuesIR.getDiagnosticMessage(); 496 if ((diagnosticMessage != null) && (! diagnosticMessage.isEmpty())) 497 { 498 err(diagnosticMessage); 499 } 500 501 502 final List<ASN1OctetString> values = streamValuesIR.getValues(); 503 if ((values != null) && (! values.isEmpty())) 504 { 505 for (final ASN1OctetString s : values) 506 { 507 outputStream.println(s.toString()); 508 } 509 510 final long updatedCount = dnsWritten.addAndGet(values.size()); 511 if (outputFile.isPresent()) 512 { 513 err(updatedCount, " DNs written."); 514 } 515 } 516 } 517}