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