001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2008 Sun Microsystems, Inc. 015 * Portions Copyright 2012-2016 ForgeRock AS. 016 */ 017package org.opends.server.tools; 018 019import static org.opends.messages.ToolMessages.*; 020import static org.opends.server.protocols.ldap.LDAPResultCode.*; 021import static org.opends.server.util.StaticUtils.*; 022import static com.forgerock.opendj.cli.CommonArguments.*; 023import static com.forgerock.opendj.cli.Utils.*; 024 025import java.io.File; 026import java.io.IOException; 027import java.io.OutputStream; 028import java.io.PrintStream; 029import java.util.HashMap; 030import java.util.LinkedHashMap; 031import java.util.LinkedList; 032import java.util.List; 033import java.util.Map; 034import java.util.TreeMap; 035 036import org.forgerock.i18n.LocalizableMessage; 037import org.forgerock.opendj.ldap.ByteString; 038import org.forgerock.opendj.ldap.DN; 039import org.forgerock.opendj.ldap.schema.AttributeType; 040import org.forgerock.opendj.ldap.schema.ObjectClass; 041import org.opends.server.core.DirectoryServer; 042import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler; 043import org.opends.server.loggers.JDKLogging; 044import org.opends.server.types.Attribute; 045import org.opends.server.types.DirectoryException; 046import org.opends.server.types.Entry; 047import org.opends.server.types.ExistingFileBehavior; 048import org.opends.server.types.InitializationException; 049import org.opends.server.types.LDAPException; 050import org.opends.server.types.LDIFExportConfig; 051import org.opends.server.types.LDIFImportConfig; 052import org.opends.server.types.Modification; 053import org.opends.server.types.NullOutputStream; 054import org.opends.server.types.RawModification; 055import org.opends.server.util.AddChangeRecordEntry; 056import org.opends.server.util.BuildVersion; 057import org.opends.server.util.ChangeRecordEntry; 058import org.opends.server.util.DeleteChangeRecordEntry; 059import org.opends.server.util.LDIFException; 060import org.opends.server.util.LDIFReader; 061import org.opends.server.util.LDIFWriter; 062import org.opends.server.util.ModifyChangeRecordEntry; 063 064import com.forgerock.opendj.cli.ArgumentException; 065import com.forgerock.opendj.cli.ArgumentParser; 066import com.forgerock.opendj.cli.BooleanArgument; 067import com.forgerock.opendj.cli.StringArgument; 068 069/** 070 * This class provides a program that may be used to apply a set of changes (in 071 * LDIF change format) to an LDIF file. It will first read all of the changes 072 * into memory, and then will iterate through an LDIF file and apply them to the 073 * entries contained in it. Note that because of the manner in which it 074 * processes the changes, certain types of operations will not be allowed, 075 * including: 076 * <BR> 077 * <UL> 078 * <LI>Modify DN operations</LI> 079 * <LI>Deleting an entry that has been added</LI> 080 * <LI>Modifying an entry that has been added</LI> 081 * </UL> 082 */ 083public class LDIFModify 084{ 085 /** 086 * The fully-qualified name of this class. 087 */ 088 private static final String CLASS_NAME = "org.opends.server.tools.LDIFModify"; 089 090 091 092 /** 093 * Applies the specified changes to the source LDIF, writing the modified 094 * file to the specified target. Neither the readers nor the writer will be 095 * closed. 096 * 097 * @param sourceReader The LDIF reader that will be used to read the LDIF 098 * content to be modified. 099 * @param changeReader The LDIF reader that will be used to read the changes 100 * to be applied. 101 * @param targetWriter The LDIF writer that will be used to write the 102 * modified LDIF. 103 * @param errorList A list into which any error messages generated while 104 * processing changes may be added. 105 * 106 * @return <CODE>true</CODE> if all updates were successfully applied, or 107 * <CODE>false</CODE> if any errors were encountered. 108 * 109 * @throws IOException If a problem occurs while attempting to read the 110 * source or changes, or write the target. 111 * 112 * @throws LDIFException If a problem occurs while attempting to decode the 113 * source or changes, or trying to determine whether 114 * to include the entry in the output. 115 */ 116 public static boolean modifyLDIF(LDIFReader sourceReader, 117 LDIFReader changeReader, 118 LDIFWriter targetWriter, 119 List<LocalizableMessage> errorList) 120 throws IOException, LDIFException 121 { 122 // Read the changes into memory. 123 TreeMap<DN,AddChangeRecordEntry> adds = new TreeMap<>(); 124 TreeMap<DN,Entry> ldifEntries = new TreeMap<>(); 125 HashMap<DN,DeleteChangeRecordEntry> deletes = new HashMap<>(); 126 HashMap<DN,LinkedList<Modification>> modifications = new HashMap<>(); 127 128 while (true) 129 { 130 ChangeRecordEntry changeRecord; 131 try 132 { 133 changeRecord = changeReader.readChangeRecord(false); 134 } 135 catch (LDIFException le) 136 { 137 if (le.canContinueReading()) 138 { 139 errorList.add(le.getMessageObject()); 140 continue; 141 } 142 else 143 { 144 throw le; 145 } 146 } 147 148 if (changeRecord == null) 149 { 150 break; 151 } 152 153 DN changeDN = changeRecord.getDN(); 154 switch (changeRecord.getChangeOperationType()) 155 { 156 case ADD: 157 // The entry must not exist in the add list. 158 if (adds.containsKey(changeDN)) 159 { 160 errorList.add(ERR_LDIFMODIFY_CANNOT_ADD_ENTRY_TWICE.get(changeDN)); 161 continue; 162 } 163 else 164 { 165 adds.put(changeDN, (AddChangeRecordEntry) changeRecord); 166 } 167 break; 168 169 case DELETE: 170 // The entry must not exist in the add list. If it exists in the 171 // modify list, then remove the changes since we won't need to apply 172 // them. 173 if (adds.containsKey(changeDN)) 174 { 175 errorList.add(ERR_LDIFMODIFY_CANNOT_DELETE_AFTER_ADD.get(changeDN)); 176 continue; 177 } 178 else 179 { 180 modifications.remove(changeDN); 181 deletes.put(changeDN, (DeleteChangeRecordEntry) changeRecord); 182 } 183 break; 184 185 case MODIFY: 186 // The entry must not exist in the add or delete lists. 187 if (adds.containsKey(changeDN) || deletes.containsKey(changeDN)) 188 { 189 errorList.add(ERR_LDIFMODIFY_CANNOT_MODIFY_ADDED_OR_DELETED.get(changeDN)); 190 continue; 191 } 192 else 193 { 194 LinkedList<Modification> mods = 195 modifications.get(changeDN); 196 if (mods == null) 197 { 198 mods = new LinkedList<>(); 199 modifications.put(changeDN, mods); 200 } 201 202 for (RawModification mod : 203 ((ModifyChangeRecordEntry) changeRecord).getModifications()) 204 { 205 try 206 { 207 mods.add(mod.toModification()); 208 } 209 catch (LDAPException le) 210 { 211 errorList.add(le.getMessageObject()); 212 continue; 213 } 214 } 215 } 216 break; 217 218 case MODIFY_DN: 219 errorList.add(ERR_LDIFMODIFY_MODDN_NOT_SUPPORTED.get(changeDN)); 220 continue; 221 222 default: 223 errorList.add(ERR_LDIFMODIFY_UNKNOWN_CHANGETYPE.get(changeDN, changeRecord.getChangeOperationType())); 224 continue; 225 } 226 } 227 228 229 // Read the source an entry at a time and apply any appropriate changes 230 // before writing to the target LDIF. 231 while (true) 232 { 233 Entry entry; 234 try 235 { 236 entry = sourceReader.readEntry(); 237 } 238 catch (LDIFException le) 239 { 240 if (le.canContinueReading()) 241 { 242 errorList.add(le.getMessageObject()); 243 continue; 244 } 245 else 246 { 247 throw le; 248 } 249 } 250 251 if (entry == null) 252 { 253 break; 254 } 255 256 257 // If the entry is to be deleted, then just skip over it without writing 258 // it to the output. 259 DN entryDN = entry.getName(); 260 if (deletes.remove(entryDN) != null) 261 { 262 continue; 263 } 264 265 266 // If the entry is to be added, then that's an error, since it already 267 // exists. 268 if (adds.remove(entryDN) != null) 269 { 270 errorList.add(ERR_LDIFMODIFY_ADD_ALREADY_EXISTS.get(entryDN)); 271 continue; 272 } 273 274 275 // If the entry is to be modified, then process the changes. 276 LinkedList<Modification> mods = modifications.remove(entryDN); 277 if (mods != null && !mods.isEmpty()) 278 { 279 try 280 { 281 entry.applyModifications(mods); 282 } 283 catch (DirectoryException de) 284 { 285 errorList.add(de.getMessageObject()); 286 continue; 287 } 288 } 289 290 291 // If we've gotten here, then the (possibly updated) entry should be 292 // written to the LDIF entry Map. 293 ldifEntries.put(entry.getName(),entry); 294 } 295 296 297 // Perform any adds that may be necessary. 298 for (AddChangeRecordEntry add : adds.values()) 299 { 300 Map<ObjectClass,String> objectClasses = new LinkedHashMap<>(); 301 Map<AttributeType,List<Attribute>> userAttributes = new LinkedHashMap<>(); 302 Map<AttributeType,List<Attribute>> operationalAttributes = new LinkedHashMap<>(); 303 304 for (Attribute a : add.getAttributes()) 305 { 306 AttributeType t = a.getAttributeDescription().getAttributeType(); 307 if (t.isObjectClass()) 308 { 309 for (ByteString v : a) 310 { 311 String ocName = v.toString(); 312 objectClasses.put(DirectoryServer.getSchema().getObjectClass(ocName), ocName); 313 } 314 } 315 else if (t.isOperational()) 316 { 317 List<Attribute> attrList = operationalAttributes.get(t); 318 if (attrList == null) 319 { 320 attrList = new LinkedList<>(); 321 operationalAttributes.put(t, attrList); 322 } 323 attrList.add(a); 324 } 325 else 326 { 327 List<Attribute> attrList = userAttributes.get(t); 328 if (attrList == null) 329 { 330 attrList = new LinkedList<>(); 331 userAttributes.put(t, attrList); 332 } 333 attrList.add(a); 334 } 335 } 336 337 Entry e = new Entry(add.getDN(), objectClasses, userAttributes, 338 operationalAttributes); 339 //Put the entry to be added into the LDIF entry map. 340 ldifEntries.put(e.getName(),e); 341 } 342 343 344 // If there are any entries left in the delete or modify lists, then that's 345 // a problem because they didn't exist. 346 if (! deletes.isEmpty()) 347 { 348 for (DN dn : deletes.keySet()) 349 { 350 errorList.add(ERR_LDIFMODIFY_DELETE_NO_SUCH_ENTRY.get(dn)); 351 } 352 } 353 354 if (! modifications.isEmpty()) 355 { 356 for (DN dn : modifications.keySet()) 357 { 358 errorList.add(ERR_LDIFMODIFY_MODIFY_NO_SUCH_ENTRY.get(dn)); 359 } 360 } 361 return targetWriter.writeEntries(ldifEntries.values()) && 362 errorList.isEmpty(); 363 } 364 365 366 367 /** 368 * Invokes <CODE>ldifModifyMain</CODE> to perform the appropriate processing. 369 * 370 * @param args The command-line arguments provided to the client. 371 */ 372 public static void main(String[] args) 373 { 374 int returnCode = ldifModifyMain(args, false, System.out, System.err); 375 if (returnCode != 0) 376 { 377 System.exit(filterExitCode(returnCode)); 378 } 379 } 380 381 382 383 /** 384 * Processes the command-line arguments and makes the appropriate updates to 385 * the LDIF file. 386 * 387 * @param args The command line arguments provided to this 388 * program. 389 * @param serverInitialized Indicates whether the Directory Server has 390 * already been initialized (and therefore should 391 * not be initialized a second time). 392 * @param outStream The output stream to use for standard output, or 393 * {@code null} if standard output is not needed. 394 * @param errStream The output stream to use for standard error, or 395 * {@code null} if standard error is not needed. 396 * 397 * @return A value of zero if everything completed properly, or nonzero if 398 * any problem(s) occurred. 399 */ 400 public static int ldifModifyMain(String[] args, boolean serverInitialized, 401 OutputStream outStream, 402 OutputStream errStream) 403 { 404 PrintStream err = NullOutputStream.wrapOrNullStream(errStream); 405 JDKLogging.disableLogging(); 406 407 // Prepare the argument parser. 408 BooleanArgument showUsage; 409 StringArgument changesFile; 410 StringArgument configFile; 411 StringArgument sourceFile; 412 StringArgument targetFile; 413 414 LocalizableMessage toolDescription = INFO_LDIFMODIFY_TOOL_DESCRIPTION.get(); 415 ArgumentParser argParser = new ArgumentParser(CLASS_NAME, toolDescription, 416 false); 417 argParser.setShortToolDescription(REF_SHORT_DESC_LDIFMODIFY.get()); 418 argParser.setVersionHandler(new DirectoryServerVersionHandler()); 419 420 try 421 { 422 configFile = 423 StringArgument.builder("configFile") 424 .shortIdentifier('c') 425 .description(INFO_DESCRIPTION_CONFIG_FILE.get()) 426 .hidden() 427 .required() 428 .valuePlaceholder(INFO_CONFIGFILE_PLACEHOLDER.get()) 429 .buildAndAddToParser(argParser); 430 sourceFile = 431 StringArgument.builder("sourceLDIF") 432 .shortIdentifier('s') 433 .description(INFO_LDIFMODIFY_DESCRIPTION_SOURCE.get()) 434 .required() 435 .valuePlaceholder(INFO_LDIFFILE_PLACEHOLDER.get()) 436 .buildAndAddToParser(argParser); 437 changesFile = 438 StringArgument.builder("changesLDIF") 439 .shortIdentifier('m') 440 .description(INFO_LDIFMODIFY_DESCRIPTION_CHANGES.get()) 441 .required() 442 .valuePlaceholder(INFO_LDIFFILE_PLACEHOLDER.get()) 443 .buildAndAddToParser(argParser); 444 targetFile = 445 StringArgument.builder("targetLDIF") 446 .shortIdentifier('t') 447 .description(INFO_LDIFMODIFY_DESCRIPTION_TARGET.get()) 448 .required() 449 .valuePlaceholder(INFO_LDIFFILE_PLACEHOLDER.get()) 450 .buildAndAddToParser(argParser); 451 452 showUsage = showUsageArgument(); 453 argParser.addArgument(showUsage); 454 argParser.setUsageArgument(showUsage); 455 } 456 catch (ArgumentException ae) 457 { 458 printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage())); 459 return 1; 460 } 461 462 463 // Parse the command-line arguments provided to the program. 464 try 465 { 466 argParser.parseArguments(args); 467 } 468 catch (ArgumentException ae) 469 { 470 argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage())); 471 return CLIENT_SIDE_PARAM_ERROR; 472 } 473 474 475 // If we should just display usage or version information, 476 // then print it and exit. 477 if (argParser.usageOrVersionDisplayed()) 478 { 479 return 0; 480 } 481 482 // Checks the version - if upgrade required, the tool is unusable 483 try 484 { 485 BuildVersion.checkVersionMismatch(); 486 } 487 catch (InitializationException e) 488 { 489 printWrappedText(err, e.getMessage()); 490 return 1; 491 } 492 493 if (! serverInitialized) 494 { 495 // Bootstrap the Directory Server configuration for use as a client. 496 DirectoryServer directoryServer = DirectoryServer.getInstance(); 497 DirectoryServer.bootstrapClient(); 498 499 500 // If we're to use the configuration then initialize it, along with the 501 // schema. 502 boolean checkSchema = configFile.isPresent(); 503 if (checkSchema) 504 { 505 try 506 { 507 DirectoryServer.initializeJMX(); 508 } 509 catch (Exception e) 510 { 511 printWrappedText(err, ERR_LDIFMODIFY_CANNOT_INITIALIZE_JMX.get(configFile.getValue(), e.getMessage())); 512 return 1; 513 } 514 515 try 516 { 517 directoryServer.initializeConfiguration(configFile.getValue()); 518 } 519 catch (Exception e) 520 { 521 printWrappedText(err, ERR_LDIFMODIFY_CANNOT_INITIALIZE_CONFIG.get(configFile.getValue(), e.getMessage())); 522 return 1; 523 } 524 525 try 526 { 527 directoryServer.initializeSchema(); 528 } 529 catch (Exception e) 530 { 531 printWrappedText(err, ERR_LDIFMODIFY_CANNOT_INITIALIZE_SCHEMA.get(configFile.getValue(), e.getMessage())); 532 return 1; 533 } 534 } 535 } 536 537 538 // Create the LDIF readers and writer from the arguments. 539 File source = new File(sourceFile.getValue()); 540 if (! source.exists()) 541 { 542 printWrappedText(err, ERR_LDIFMODIFY_SOURCE_DOES_NOT_EXIST.get(sourceFile.getValue())); 543 return CLIENT_SIDE_PARAM_ERROR; 544 } 545 546 LDIFImportConfig importConfig = new LDIFImportConfig(sourceFile.getValue()); 547 LDIFReader sourceReader; 548 try 549 { 550 sourceReader = new LDIFReader(importConfig); 551 } 552 catch (IOException ioe) 553 { 554 printWrappedText(err, ERR_LDIFMODIFY_CANNOT_OPEN_SOURCE.get(sourceFile.getValue(), ioe)); 555 return CLIENT_SIDE_LOCAL_ERROR; 556 } 557 558 559 File changes = new File(changesFile.getValue()); 560 if (! changes.exists()) 561 { 562 printWrappedText(err, ERR_LDIFMODIFY_CHANGES_DOES_NOT_EXIST.get(changesFile.getValue())); 563 return CLIENT_SIDE_PARAM_ERROR; 564 } 565 566 importConfig = new LDIFImportConfig(changesFile.getValue()); 567 LDIFReader changeReader; 568 try 569 { 570 changeReader = new LDIFReader(importConfig); 571 } 572 catch (IOException ioe) 573 { 574 printWrappedText(err, ERR_LDIFMODIFY_CANNOT_OPEN_CHANGES.get(sourceFile.getValue(), ioe.getMessage())); 575 return CLIENT_SIDE_LOCAL_ERROR; 576 } 577 578 579 LDIFExportConfig exportConfig = 580 new LDIFExportConfig(targetFile.getValue(), 581 ExistingFileBehavior.OVERWRITE); 582 LDIFWriter targetWriter; 583 try 584 { 585 targetWriter = new LDIFWriter(exportConfig); 586 } 587 catch (IOException ioe) 588 { 589 printWrappedText(err, ERR_LDIFMODIFY_CANNOT_OPEN_TARGET.get(sourceFile.getValue(), ioe.getMessage())); 590 return CLIENT_SIDE_LOCAL_ERROR; 591 } 592 593 594 // Actually invoke the LDIF processing. 595 LinkedList<LocalizableMessage> errorList = new LinkedList<>(); 596 boolean successful; 597 try 598 { 599 successful = modifyLDIF(sourceReader, changeReader, targetWriter, errorList); 600 } 601 catch (Exception e) 602 { 603 err.println(ERR_LDIFMODIFY_ERROR_PROCESSING_LDIF.get(e)); 604 successful = false; 605 } 606 607 close(sourceReader, changeReader, targetWriter); 608 609 for (LocalizableMessage s : errorList) 610 { 611 err.println(s); 612 } 613 return successful ? 0 : 1; 614 } 615}