001 /*
002 * Copyright 2008-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2013 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.examples;
022
023
024
025 import java.io.IOException;
026 import java.io.OutputStream;
027 import java.io.Serializable;
028 import java.util.LinkedHashMap;
029
030 import com.unboundid.ldap.sdk.LDAPConnection;
031 import com.unboundid.ldap.sdk.LDAPException;
032 import com.unboundid.ldap.sdk.ResultCode;
033 import com.unboundid.ldap.sdk.Version;
034 import com.unboundid.ldif.LDIFChangeRecord;
035 import com.unboundid.ldif.LDIFException;
036 import com.unboundid.ldif.LDIFReader;
037 import com.unboundid.util.LDAPCommandLineTool;
038 import com.unboundid.util.ThreadSafety;
039 import com.unboundid.util.ThreadSafetyLevel;
040 import com.unboundid.util.args.ArgumentException;
041 import com.unboundid.util.args.ArgumentParser;
042 import com.unboundid.util.args.BooleanArgument;
043 import com.unboundid.util.args.FileArgument;
044
045
046
047 /**
048 * This class provides a simple tool that can be used to perform add, delete,
049 * modify, and modify DN operations against an LDAP directory server. The
050 * changes to apply can be read either from standard input or from an LDIF file.
051 * <BR><BR>
052 * Some of the APIs demonstrated by this example include:
053 * <UL>
054 * <LI>Argument Parsing (from the {@code com.unboundid.util.args}
055 * package)</LI>
056 * <LI>LDAP Command-Line Tool (from the {@code com.unboundid.util}
057 * package)</LI>
058 * <LI>LDIF Processing (from the {@code com.unboundid.ldif} package)</LI>
059 * </UL>
060 * <BR><BR>
061 * The behavior of this utility is controlled by command line arguments.
062 * Supported arguments include those allowed by the {@link LDAPCommandLineTool}
063 * class, as well as the following additional arguments:
064 * <UL>
065 * <LI>"-f {path}" or "--ldifFile {path}" -- specifies the path to the LDIF
066 * file containing the changes to apply. If this is not provided, then
067 * changes will be read from standard input.</LI>
068 * <LI>"-a" or "--defaultAdd" -- indicates that any LDIF records encountered
069 * that do not include a changetype should be treated as add change
070 * records. If this is not provided, then such records will be
071 * rejected.</LI>
072 * <LI>"-c" or "--continueOnError" -- indicates that processing should
073 * continue if an error occurs while processing an earlier change. If
074 * this is not provided, then the command will exit on the first error
075 * that occurs.</LI>
076 * </UL>
077 */
078 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
079 public final class LDAPModify
080 extends LDAPCommandLineTool
081 implements Serializable
082 {
083 /**
084 * The serial version UID for this serializable class.
085 */
086 private static final long serialVersionUID = -2602159836108416722L;
087
088
089
090 // Indicates whether processing should continue even if an error has occurred.
091 private BooleanArgument continueOnError;
092
093 // Indicates whether LDIF records without a changetype should be considered
094 // add records.
095 private BooleanArgument defaultAdd;
096
097 // The LDIF file to be processed.
098 private FileArgument ldifFile;
099
100
101
102 /**
103 * Parse the provided command line arguments and make the appropriate set of
104 * changes.
105 *
106 * @param args The command line arguments provided to this program.
107 */
108 public static void main(final String[] args)
109 {
110 final ResultCode resultCode = main(args, System.out, System.err);
111 if (resultCode != ResultCode.SUCCESS)
112 {
113 System.exit(resultCode.intValue());
114 }
115 }
116
117
118
119 /**
120 * Parse the provided command line arguments and make the appropriate set of
121 * changes.
122 *
123 * @param args The command line arguments provided to this program.
124 * @param outStream The output stream to which standard out should be
125 * written. It may be {@code null} if output should be
126 * suppressed.
127 * @param errStream The output stream to which standard error should be
128 * written. It may be {@code null} if error messages
129 * should be suppressed.
130 *
131 * @return A result code indicating whether the processing was successful.
132 */
133 public static ResultCode main(final String[] args,
134 final OutputStream outStream,
135 final OutputStream errStream)
136 {
137 final LDAPModify ldapModify = new LDAPModify(outStream, errStream);
138 return ldapModify.runTool(args);
139 }
140
141
142
143 /**
144 * Creates a new instance of this tool.
145 *
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 public LDAPModify(final OutputStream outStream, final OutputStream errStream)
154 {
155 super(outStream, errStream);
156 }
157
158
159
160 /**
161 * Retrieves the name for this tool.
162 *
163 * @return The name for this tool.
164 */
165 @Override()
166 public String getToolName()
167 {
168 return "ldapmodify";
169 }
170
171
172
173 /**
174 * Retrieves the description for this tool.
175 *
176 * @return The description for this tool.
177 */
178 @Override()
179 public String getToolDescription()
180 {
181 return "Perform add, delete, modify, and modify " +
182 "DN operations in an LDAP directory server.";
183 }
184
185
186
187 /**
188 * Retrieves the version string for this tool.
189 *
190 * @return The version string for this tool.
191 */
192 @Override()
193 public String getToolVersion()
194 {
195 return Version.NUMERIC_VERSION_STRING;
196 }
197
198
199
200 /**
201 * Adds the arguments used by this program that aren't already provided by the
202 * generic {@code LDAPCommandLineTool} framework.
203 *
204 * @param parser The argument parser to which the arguments should be added.
205 *
206 * @throws ArgumentException If a problem occurs while adding the arguments.
207 */
208 @Override()
209 public void addNonLDAPArguments(final ArgumentParser parser)
210 throws ArgumentException
211 {
212 String description = "Treat LDIF records that do not contain a " +
213 "changetype as add records.";
214 defaultAdd = new BooleanArgument('a', "defaultAdd", description);
215 parser.addArgument(defaultAdd);
216
217
218 description = "Attempt to continue processing additional changes if " +
219 "an error occurs.";
220 continueOnError = new BooleanArgument('c', "continueOnError",
221 description);
222 parser.addArgument(continueOnError);
223
224
225 description = "The path to the LDIF file containing the changes. If " +
226 "this is not provided, then the changes will be read from " +
227 "standard input.";
228 ldifFile = new FileArgument('f', "ldifFile", false, 1, "{path}",
229 description, true, false, true, false);
230 parser.addArgument(ldifFile);
231 }
232
233
234
235 /**
236 * Performs the actual processing for this tool. In this case, it gets a
237 * connection to the directory server and uses it to perform the requested
238 * operations.
239 *
240 * @return The result code for the processing that was performed.
241 */
242 @Override()
243 public ResultCode doToolProcessing()
244 {
245 // Set up the LDIF reader that will be used to read the changes to apply.
246 final LDIFReader ldifReader;
247 try
248 {
249 if (ldifFile.isPresent())
250 {
251 // An LDIF file was specified on the command line, so we will use it.
252 ldifReader = new LDIFReader(ldifFile.getValue());
253 }
254 else
255 {
256 // No LDIF file was specified, so we will read from standard input.
257 ldifReader = new LDIFReader(System.in);
258 }
259 }
260 catch (IOException ioe)
261 {
262 err("I/O error creating the LDIF reader: ", ioe.getMessage());
263 return ResultCode.LOCAL_ERROR;
264 }
265
266
267 // Get the connection to the directory server.
268 final LDAPConnection connection;
269 try
270 {
271 connection = getConnection();
272 out("Connected to ", connection.getConnectedAddress(), ':',
273 connection.getConnectedPort());
274 }
275 catch (LDAPException le)
276 {
277 err("Error connecting to the directory server: ", le.getMessage());
278 return le.getResultCode();
279 }
280
281
282 // Attempt to process and apply the changes to the server.
283 ResultCode resultCode = ResultCode.SUCCESS;
284 while (true)
285 {
286 // Read the next change to process.
287 final LDIFChangeRecord changeRecord;
288 try
289 {
290 changeRecord = ldifReader.readChangeRecord(defaultAdd.isPresent());
291 }
292 catch (LDIFException le)
293 {
294 err("Malformed change record: ", le.getMessage());
295 if (! le.mayContinueReading())
296 {
297 err("Unable to continue processing the LDIF content.");
298 resultCode = ResultCode.DECODING_ERROR;
299 break;
300 }
301 else if (! continueOnError.isPresent())
302 {
303 resultCode = ResultCode.DECODING_ERROR;
304 break;
305 }
306 else
307 {
308 // We can try to keep processing, so do so.
309 continue;
310 }
311 }
312 catch (IOException ioe)
313 {
314 err("I/O error encountered while reading a change record: ",
315 ioe.getMessage());
316 resultCode = ResultCode.LOCAL_ERROR;
317 break;
318 }
319
320
321 // If the change record was null, then it means there are no more changes
322 // to be processed.
323 if (changeRecord == null)
324 {
325 break;
326 }
327
328
329 // Apply the target change to the server.
330 try
331 {
332 out("Processing ", changeRecord.getChangeType().toString(),
333 " operation for ", changeRecord.getDN());
334 changeRecord.processChange(connection);
335 out("Success");
336 out();
337 }
338 catch (LDAPException le)
339 {
340 err("Error: ", le.getMessage());
341 err("Result Code: ", le.getResultCode().intValue(), " (",
342 le.getResultCode().getName(), ')');
343 if (le.getMatchedDN() != null)
344 {
345 err("Matched DN: ", le.getMatchedDN());
346 }
347
348 if (le.getReferralURLs() != null)
349 {
350 for (final String url : le.getReferralURLs())
351 {
352 err("Referral URL: ", url);
353 }
354 }
355
356 err();
357 if (! continueOnError.isPresent())
358 {
359 resultCode = le.getResultCode();
360 break;
361 }
362 }
363 }
364
365
366 // Close the connection to the directory server and exit.
367 connection.close();
368 out("Disconnected from the server");
369 return resultCode;
370 }
371
372
373
374 /**
375 * {@inheritDoc}
376 */
377 @Override()
378 public LinkedHashMap<String[],String> getExampleUsages()
379 {
380 final LinkedHashMap<String[],String> examples =
381 new LinkedHashMap<String[],String>();
382
383 String[] args =
384 {
385 "--hostname", "server.example.com",
386 "--port", "389",
387 "--bindDN", "uid=admin,dc=example,dc=com",
388 "--bindPassword", "password",
389 "--ldifFile", "changes.ldif"
390 };
391 String description =
392 "Attempt to apply the add, delete, modify, and/or modify DN " +
393 "operations contained in the 'changes.ldif' file against the " +
394 "specified directory server.";
395 examples.put(args, description);
396
397 args = new String[]
398 {
399 "--hostname", "server.example.com",
400 "--port", "389",
401 "--bindDN", "uid=admin,dc=example,dc=com",
402 "--bindPassword", "password",
403 "--continueOnError",
404 "--defaultAdd"
405 };
406 description =
407 "Establish a connection to the specified directory server and then " +
408 "wait for information about the add, delete, modify, and/or modify " +
409 "DN operations to perform to be provided via standard input. If " +
410 "any invalid operations are requested, then the tool will display " +
411 "an error message but will continue running. Any LDIF record " +
412 "provided which does not include a 'changeType' line will be " +
413 "treated as an add request.";
414 examples.put(args, description);
415
416 return examples;
417 }
418 }