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