001/* 002 * Copyright 2008-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2014 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 */ 021package com.unboundid.ldap.sdk.examples; 022 023 024 025import java.io.IOException; 026import java.io.OutputStream; 027import java.io.Serializable; 028import java.util.LinkedHashMap; 029 030import com.unboundid.ldap.sdk.LDAPConnection; 031import com.unboundid.ldap.sdk.LDAPException; 032import com.unboundid.ldap.sdk.ResultCode; 033import com.unboundid.ldap.sdk.Version; 034import com.unboundid.ldif.LDIFChangeRecord; 035import com.unboundid.ldif.LDIFException; 036import com.unboundid.ldif.LDIFReader; 037import com.unboundid.util.LDAPCommandLineTool; 038import com.unboundid.util.ThreadSafety; 039import com.unboundid.util.ThreadSafetyLevel; 040import com.unboundid.util.args.ArgumentException; 041import com.unboundid.util.args.ArgumentParser; 042import com.unboundid.util.args.BooleanArgument; 043import 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) 079public 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}