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