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