001 /*
002 * Copyright 2010-2015 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2015 UnboundID Corp.
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 */
021 package com.unboundid.ldap.sdk.unboundidds.examples;
022
023
024
025 import java.io.BufferedOutputStream;
026 import java.io.File;
027 import java.io.FileOutputStream;
028 import java.io.IOException;
029 import java.io.OutputStream;
030 import java.io.PrintStream;
031 import java.util.LinkedHashMap;
032 import java.util.List;
033 import java.util.concurrent.atomic.AtomicLong;
034
035 import com.unboundid.asn1.ASN1OctetString;
036 import com.unboundid.ldap.sdk.ExtendedResult;
037 import com.unboundid.ldap.sdk.LDAPConnection;
038 import com.unboundid.ldap.sdk.LDAPException;
039 import com.unboundid.ldap.sdk.IntermediateResponse;
040 import com.unboundid.ldap.sdk.IntermediateResponseListener;
041 import com.unboundid.ldap.sdk.ResultCode;
042 import com.unboundid.ldap.sdk.SearchScope;
043 import com.unboundid.ldap.sdk.Version;
044 import com.unboundid.ldap.sdk.unboundidds.extensions.
045 StreamDirectoryValuesExtendedRequest;
046 import com.unboundid.ldap.sdk.unboundidds.extensions.
047 StreamDirectoryValuesIntermediateResponse;
048 import com.unboundid.util.LDAPCommandLineTool;
049 import com.unboundid.util.ThreadSafety;
050 import com.unboundid.util.ThreadSafetyLevel;
051 import com.unboundid.util.args.ArgumentException;
052 import com.unboundid.util.args.ArgumentParser;
053 import com.unboundid.util.args.DNArgument;
054 import com.unboundid.util.args.FileArgument;
055
056
057
058 /**
059 * <BLOCKQUOTE>
060 * <B>NOTE:</B> This class is part of the Commercial Edition of the UnboundID
061 * LDAP SDK for Java. It is not available for use in applications that
062 * include only the Standard Edition of the LDAP SDK, and is not supported for
063 * use in conjunction with non-UnboundID products.
064 * </BLOCKQUOTE>
065 * This class provides a utility that uses the stream directory values extended
066 * operation in order to obtain a listing of all entry DNs below a specified
067 * base DN in the Directory Server.
068 * <BR><BR>
069 * The APIs demonstrated by this example include:
070 * <UL>
071 * <LI>The use of the stream directory values extended operation.</LI>
072 * <LI>Intermediate response processing.</LI>
073 * <LI>The LDAP command-line tool API.</LI>
074 * <LI>Argument parsing.</LI>
075 * </UL>
076 */
077 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
078 public final class DumpDNs
079 extends LDAPCommandLineTool
080 implements IntermediateResponseListener
081 {
082 /**
083 * The serial version UID for this serializable class.
084 */
085 private static final long serialVersionUID = 774432759537092866L;
086
087
088
089 // The argument used to obtain the base DN.
090 private DNArgument baseDN;
091
092 // The argument used to obtain the output file.
093 private FileArgument outputFile;
094
095 // The number of DNs dumped.
096 private final AtomicLong dnsWritten;
097
098 // The print stream that will be used to output the DNs.
099 private PrintStream outputStream;
100
101
102
103 /**
104 * Parse the provided command line arguments and perform the appropriate
105 * processing.
106 *
107 * @param args The command line arguments provided to this program.
108 */
109 public static void main(final String[] args)
110 {
111 final ResultCode resultCode = main(args, System.out, System.err);
112 if (resultCode != ResultCode.SUCCESS)
113 {
114 System.exit(resultCode.intValue());
115 }
116 }
117
118
119
120 /**
121 * Parse the provided command line arguments and perform the appropriate
122 * processing.
123 *
124 * @param args The command line arguments provided to this program.
125 * @param outStream The output stream to which standard out should be
126 * written. It may be {@code null} if output should be
127 * suppressed.
128 * @param errStream The output stream to which standard error should be
129 * written. It may be {@code null} if error messages
130 * should be suppressed.
131 *
132 * @return A result code indicating whether the processing was successful.
133 */
134 public static ResultCode main(final String[] args,
135 final OutputStream outStream,
136 final OutputStream errStream)
137 {
138 final DumpDNs tool = new DumpDNs(outStream, errStream);
139 return tool.runTool(args);
140 }
141
142
143
144 /**
145 * Creates a new instance of this tool.
146 *
147 * @param outStream The output stream to which standard out should be
148 * written. It may be {@code null} if output should be
149 * suppressed.
150 * @param errStream The output stream to which standard error should be
151 * written. It may be {@code null} if error messages
152 * should be suppressed.
153 */
154 public DumpDNs(final OutputStream outStream, final OutputStream errStream)
155 {
156 super(outStream, errStream);
157
158 baseDN = null;
159 outputFile = null;
160 outputStream = null;
161 dnsWritten = new AtomicLong(0L);
162 }
163
164
165
166
167 /**
168 * Retrieves the name of this tool. It should be the name of the command used
169 * to invoke this tool.
170 *
171 * @return The name for this tool.
172 */
173 @Override()
174 public String getToolName()
175 {
176 return "dump-dns";
177 }
178
179
180
181 /**
182 * Retrieves a human-readable description for this tool.
183 *
184 * @return A human-readable description for this tool.
185 */
186 @Override()
187 public String getToolDescription()
188 {
189 return "Obtain a listing of all of the DNs for all entries below a " +
190 "specified base DN in the Directory Server.";
191 }
192
193
194
195 /**
196 * Retrieves the version string for this tool.
197 *
198 * @return The version string for this tool.
199 */
200 @Override()
201 public String getToolVersion()
202 {
203 return Version.NUMERIC_VERSION_STRING;
204 }
205
206
207
208 /**
209 * Adds the arguments needed by this command-line tool to the provided
210 * argument parser which are not related to connecting or authenticating to
211 * the directory server.
212 *
213 * @param parser The argument parser to which the arguments should be added.
214 *
215 * @throws ArgumentException If a problem occurs while adding the arguments.
216 */
217 @Override()
218 public void addNonLDAPArguments(final ArgumentParser parser)
219 throws ArgumentException
220 {
221 baseDN = new DNArgument('b', "baseDN", true, 1, "{dn}",
222 "The base DN below which to dump the DNs of all entries in the " +
223 "Directory Server.");
224 parser.addArgument(baseDN);
225
226 outputFile = new FileArgument('f', "outputFile", false, 1, "{path}",
227 "The path of the output file to which the entry DNs will be " +
228 "written. If this is not provided, then entry DNs will be " +
229 "written to standard output.", false, true, true, false);
230 parser.addArgument(outputFile);
231 }
232
233
234
235 /**
236 * Performs the core set of processing for this tool.
237 *
238 * @return A result code that indicates whether the processing completed
239 * successfully.
240 */
241 @Override()
242 public ResultCode doToolProcessing()
243 {
244 // Create the writer that will be used to write the DNs.
245 final File f = outputFile.getValue();
246 if (f == null)
247 {
248 outputStream = getOut();
249 }
250 else
251 {
252 try
253 {
254 outputStream =
255 new PrintStream(new BufferedOutputStream(new FileOutputStream(f)));
256 }
257 catch (final IOException ioe)
258 {
259 err("Unable to open output file '", f.getAbsolutePath(),
260 " for writing: ", String.valueOf(ioe));
261 return ResultCode.LOCAL_ERROR;
262 }
263 }
264
265
266 // Obtain a connection to the Directory Server.
267 final LDAPConnection conn;
268 try
269 {
270 conn = getConnection();
271 }
272 catch (final LDAPException le)
273 {
274 err("Unable to obtain a connection to the Directory Server: ",
275 le.getExceptionMessage());
276 return le.getResultCode();
277 }
278
279
280 // Create the extended request. Register this class as an intermediate
281 // response listener, and indicate that we don't want any response time
282 // limit.
283 final StreamDirectoryValuesExtendedRequest streamValuesRequest =
284 new StreamDirectoryValuesExtendedRequest(baseDN.getStringValue(),
285 SearchScope.SUB, false, null, 1000);
286 streamValuesRequest.setIntermediateResponseListener(this);
287 streamValuesRequest.setResponseTimeoutMillis(0L);
288
289
290 // Send the extended request to the server and get the result.
291 try
292 {
293 final ExtendedResult streamValuesResult =
294 conn.processExtendedOperation(streamValuesRequest);
295 err("Processing completed. ", dnsWritten.get(), " DNs written.");
296 return streamValuesResult.getResultCode();
297 }
298 catch (final LDAPException le)
299 {
300 err("Unable to send the stream directory values extended request to " +
301 "the Directory Server: ", le.getExceptionMessage());
302 return le.getResultCode();
303 }
304 finally
305 {
306 if (f != null)
307 {
308 outputStream.close();
309 }
310
311 conn.close();
312 }
313 }
314
315
316
317 /**
318 * Retrieves a set of information that may be used to generate example usage
319 * information. Each element in the returned map should consist of a map
320 * between an example set of arguments and a string that describes the
321 * behavior of the tool when invoked with that set of arguments.
322 *
323 * @return A set of information that may be used to generate example usage
324 * information. It may be {@code null} or empty if no example usage
325 * information is available.
326 */
327 @Override()
328 public LinkedHashMap<String[],String> getExampleUsages()
329 {
330 final LinkedHashMap<String[],String> exampleMap =
331 new LinkedHashMap<String[],String>(1);
332
333 final String[] args =
334 {
335 "--hostname", "server.example.com",
336 "--port", "389",
337 "--bindDN", "uid=admin,dc=example,dc=com",
338 "--bindPassword", "password",
339 "--baseDN", "dc=example,dc=com",
340 "--outputFile", "example-dns.txt",
341 };
342 exampleMap.put(args,
343 "Dump all entry DNs at or below 'dc=example,dc=com' to the file " +
344 "'example-dns.txt'");
345
346 return exampleMap;
347 }
348
349
350
351 /**
352 * Indicates that the provided intermediate response has been returned by the
353 * server and may be processed by this intermediate response listener. In
354 * this case, it will
355 *
356 * @param intermediateResponse The intermediate response that has been
357 * returned by the server.
358 */
359 public void intermediateResponseReturned(
360 final IntermediateResponse intermediateResponse)
361 {
362 // Try to parse the intermediate response as a stream directory values
363 // intermediate response.
364 final StreamDirectoryValuesIntermediateResponse streamValuesIR;
365 try
366 {
367 streamValuesIR =
368 new StreamDirectoryValuesIntermediateResponse(intermediateResponse);
369 }
370 catch (final LDAPException le)
371 {
372 err("Unable to parse an intermediate response message as a stream " +
373 "directory values intermediate response: ",
374 le.getExceptionMessage());
375 return;
376 }
377
378 final String diagnosticMessage = streamValuesIR.getDiagnosticMessage();
379 if ((diagnosticMessage != null) && (diagnosticMessage.length() > 0))
380 {
381 err(diagnosticMessage);
382 }
383
384
385 final List<ASN1OctetString> values = streamValuesIR.getValues();
386 if ((values != null) && (! values.isEmpty()))
387 {
388 for (final ASN1OctetString s : values)
389 {
390 outputStream.println(s.toString());
391 }
392
393 final long updatedCount = dnsWritten.addAndGet(values.size());
394 if (outputFile.isPresent())
395 {
396 err(updatedCount, " DNs written.");
397 }
398 }
399 }
400 }