001/* 002 * Copyright 2010-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2015-2018 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 Alcatel-Lucent 8661 069 * server products. These classes provide support for proprietary 070 * functionality or for external specifications that are not considered stable 071 * or mature enough to be guaranteed to work in an interoperable way with 072 * 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 /** 174 * Retrieves the name of this tool. It should be the name of the command used 175 * to invoke this tool. 176 * 177 * @return The name for this tool. 178 */ 179 @Override() 180 public String getToolName() 181 { 182 return "dump-dns"; 183 } 184 185 186 187 /** 188 * Retrieves a human-readable description for this tool. 189 * 190 * @return A human-readable description for this tool. 191 */ 192 @Override() 193 public String getToolDescription() 194 { 195 return "Obtain a listing of all of the DNs for all entries below a " + 196 "specified base DN in the Directory Server."; 197 } 198 199 200 201 /** 202 * Retrieves the version string for this tool. 203 * 204 * @return The version string for this tool. 205 */ 206 @Override() 207 public String getToolVersion() 208 { 209 return Version.NUMERIC_VERSION_STRING; 210 } 211 212 213 214 /** 215 * Indicates whether this tool should provide support for an interactive mode, 216 * in which the tool offers a mode in which the arguments can be provided in 217 * a text-driven menu rather than requiring them to be given on the command 218 * line. If interactive mode is supported, it may be invoked using the 219 * "--interactive" argument. Alternately, if interactive mode is supported 220 * and {@link #defaultsToInteractiveMode()} returns {@code true}, then 221 * interactive mode may be invoked by simply launching the tool without any 222 * arguments. 223 * 224 * @return {@code true} if this tool supports interactive mode, or 225 * {@code false} if not. 226 */ 227 @Override() 228 public boolean supportsInteractiveMode() 229 { 230 return true; 231 } 232 233 234 235 /** 236 * Indicates whether this tool defaults to launching in interactive mode if 237 * the tool is invoked without any command-line arguments. This will only be 238 * used if {@link #supportsInteractiveMode()} returns {@code true}. 239 * 240 * @return {@code true} if this tool defaults to using interactive mode if 241 * launched without any command-line arguments, or {@code false} if 242 * not. 243 */ 244 @Override() 245 public boolean defaultsToInteractiveMode() 246 { 247 return true; 248 } 249 250 251 252 /** 253 * Indicates whether this tool should default to interactively prompting for 254 * the bind password if a password is required but no argument was provided 255 * to indicate how to get the password. 256 * 257 * @return {@code true} if this tool should default to interactively 258 * prompting for the bind password, or {@code false} if not. 259 */ 260 @Override() 261 protected boolean defaultToPromptForBindPassword() 262 { 263 return true; 264 } 265 266 267 268 /** 269 * Indicates whether this tool supports the use of a properties file for 270 * specifying default values for arguments that aren't specified on the 271 * command line. 272 * 273 * @return {@code true} if this tool supports the use of a properties file 274 * for specifying default values for arguments that aren't specified 275 * on the command line, or {@code false} if not. 276 */ 277 @Override() 278 public boolean supportsPropertiesFile() 279 { 280 return true; 281 } 282 283 284 285 /** 286 * Indicates whether the LDAP-specific arguments should include alternate 287 * versions of all long identifiers that consist of multiple words so that 288 * they are available in both camelCase and dash-separated versions. 289 * 290 * @return {@code true} if this tool should provide multiple versions of 291 * long identifiers for LDAP-specific arguments, or {@code false} if 292 * not. 293 */ 294 @Override() 295 protected boolean includeAlternateLongIdentifiers() 296 { 297 return true; 298 } 299 300 301 302 /** 303 * Adds the arguments needed by this command-line tool to the provided 304 * argument parser which are not related to connecting or authenticating to 305 * the directory server. 306 * 307 * @param parser The argument parser to which the arguments should be added. 308 * 309 * @throws ArgumentException If a problem occurs while adding the arguments. 310 */ 311 @Override() 312 public void addNonLDAPArguments(final ArgumentParser parser) 313 throws ArgumentException 314 { 315 baseDN = new DNArgument('b', "baseDN", true, 1, "{dn}", 316 "The base DN below which to dump the DNs of all entries in the " + 317 "Directory Server."); 318 baseDN.addLongIdentifier("base-dn", true); 319 parser.addArgument(baseDN); 320 321 outputFile = new FileArgument('f', "outputFile", false, 1, "{path}", 322 "The path of the output file to which the entry DNs will be " + 323 "written. If this is not provided, then entry DNs will be " + 324 "written to standard output.", false, true, true, false); 325 outputFile.addLongIdentifier("output-file", true); 326 parser.addArgument(outputFile); 327 } 328 329 330 331 /** 332 * Retrieves the connection options that should be used for connections that 333 * are created with this command line tool. Subclasses may override this 334 * method to use a custom set of connection options. 335 * 336 * @return The connection options that should be used for connections that 337 * are created with this command line tool. 338 */ 339 @Override() 340 public LDAPConnectionOptions getConnectionOptions() 341 { 342 final LDAPConnectionOptions options = new LDAPConnectionOptions(); 343 344 options.setUseSynchronousMode(true); 345 options.setResponseTimeoutMillis(0L); 346 347 return options; 348 } 349 350 351 352 /** 353 * Performs the core set of processing for this tool. 354 * 355 * @return A result code that indicates whether the processing completed 356 * successfully. 357 */ 358 @Override() 359 public ResultCode doToolProcessing() 360 { 361 // Create the writer that will be used to write the DNs. 362 final File f = outputFile.getValue(); 363 if (f == null) 364 { 365 outputStream = getOut(); 366 } 367 else 368 { 369 try 370 { 371 outputStream = 372 new PrintStream(new BufferedOutputStream(new FileOutputStream(f))); 373 } 374 catch (final IOException ioe) 375 { 376 err("Unable to open output file '", f.getAbsolutePath(), 377 " for writing: ", StaticUtils.getExceptionMessage(ioe)); 378 return ResultCode.LOCAL_ERROR; 379 } 380 } 381 382 383 // Obtain a connection to the Directory Server. 384 final LDAPConnection conn; 385 try 386 { 387 conn = getConnection(); 388 } 389 catch (final LDAPException le) 390 { 391 err("Unable to obtain a connection to the Directory Server: ", 392 le.getExceptionMessage()); 393 return le.getResultCode(); 394 } 395 396 397 // Create the extended request. Register this class as an intermediate 398 // response listener, and indicate that we don't want any response time 399 // limit. 400 final StreamDirectoryValuesExtendedRequest streamValuesRequest = 401 new StreamDirectoryValuesExtendedRequest(baseDN.getStringValue(), 402 SearchScope.SUB, false, null, 1000); 403 streamValuesRequest.setIntermediateResponseListener(this); 404 streamValuesRequest.setResponseTimeoutMillis(0L); 405 406 407 // Send the extended request to the server and get the result. 408 try 409 { 410 final ExtendedResult streamValuesResult = 411 conn.processExtendedOperation(streamValuesRequest); 412 err("Processing completed. ", dnsWritten.get(), " DNs written."); 413 return streamValuesResult.getResultCode(); 414 } 415 catch (final LDAPException le) 416 { 417 err("Unable to send the stream directory values extended request to " + 418 "the Directory Server: ", le.getExceptionMessage()); 419 return le.getResultCode(); 420 } 421 finally 422 { 423 if (f != null) 424 { 425 outputStream.close(); 426 } 427 428 conn.close(); 429 } 430 } 431 432 433 434 /** 435 * Retrieves a set of information that may be used to generate example usage 436 * information. Each element in the returned map should consist of a map 437 * between an example set of arguments and a string that describes the 438 * behavior of the tool when invoked with that set of arguments. 439 * 440 * @return A set of information that may be used to generate example usage 441 * information. It may be {@code null} or empty if no example usage 442 * information is available. 443 */ 444 @Override() 445 public LinkedHashMap<String[],String> getExampleUsages() 446 { 447 final LinkedHashMap<String[],String> exampleMap = 448 new LinkedHashMap<String[],String>(1); 449 450 final String[] args = 451 { 452 "--hostname", "server.example.com", 453 "--port", "389", 454 "--bindDN", "uid=admin,dc=example,dc=com", 455 "--bindPassword", "password", 456 "--baseDN", "dc=example,dc=com", 457 "--outputFile", "example-dns.txt", 458 }; 459 exampleMap.put(args, 460 "Dump all entry DNs at or below 'dc=example,dc=com' to the file " + 461 "'example-dns.txt'"); 462 463 return exampleMap; 464 } 465 466 467 468 /** 469 * Indicates that the provided intermediate response has been returned by the 470 * server and may be processed by this intermediate response listener. In 471 * this case, it will 472 * 473 * @param intermediateResponse The intermediate response that has been 474 * returned by the server. 475 */ 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.length() > 0)) 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}