001/* 002 * Copyright 2007-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; 022 023 024 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.Collections; 028import java.util.List; 029import java.util.StringTokenizer; 030 031import com.unboundid.ldif.LDIFAddChangeRecord; 032import com.unboundid.ldif.LDIFChangeRecord; 033import com.unboundid.ldif.LDIFDeleteChangeRecord; 034import com.unboundid.ldif.LDIFException; 035import com.unboundid.ldif.LDIFModifyChangeRecord; 036import com.unboundid.ldif.LDIFModifyDNChangeRecord; 037import com.unboundid.ldif.LDIFReader; 038import com.unboundid.ldap.matchingrules.BooleanMatchingRule; 039import com.unboundid.ldap.matchingrules.DistinguishedNameMatchingRule; 040import com.unboundid.ldap.matchingrules.IntegerMatchingRule; 041import com.unboundid.ldap.matchingrules.OctetStringMatchingRule; 042import com.unboundid.util.Debug; 043import com.unboundid.util.NotExtensible; 044import com.unboundid.util.NotMutable; 045import com.unboundid.util.ThreadSafety; 046import com.unboundid.util.ThreadSafetyLevel; 047 048import static com.unboundid.ldap.sdk.LDAPMessages.*; 049import static com.unboundid.util.StaticUtils.*; 050 051 052 053/** 054 * This class provides a data structure for representing a changelog entry as 055 * described in draft-good-ldap-changelog. Changelog entries provide 056 * information about a change (add, delete, modify, or modify DN) operation 057 * that was processed in the directory server. Changelog entries may be 058 * parsed from entries, and they may be converted to LDIF change records or 059 * processed as LDAP operations. 060 */ 061@NotExtensible() 062@NotMutable() 063@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 064public class ChangeLogEntry 065 extends ReadOnlyEntry 066{ 067 /** 068 * The name of the attribute that contains the change number that identifies 069 * the change and the order it was processed in the server. 070 */ 071 public static final String ATTR_CHANGE_NUMBER = "changeNumber"; 072 073 074 075 /** 076 * The name of the attribute that contains the DN of the entry targeted by 077 * the change. 078 */ 079 public static final String ATTR_TARGET_DN = "targetDN"; 080 081 082 083 /** 084 * The name of the attribute that contains the type of change made to the 085 * target entry. 086 */ 087 public static final String ATTR_CHANGE_TYPE = "changeType"; 088 089 090 091 /** 092 * The name of the attribute used to hold a list of changes. For an add 093 * operation, this will be an LDIF representation of the attributes that make 094 * up the entry. For a modify operation, this will be an LDIF representation 095 * of the changes to the target entry. 096 */ 097 public static final String ATTR_CHANGES = "changes"; 098 099 100 101 /** 102 * The name of the attribute used to hold the new RDN for a modify DN 103 * operation. 104 */ 105 public static final String ATTR_NEW_RDN = "newRDN"; 106 107 108 109 /** 110 * The name of the attribute used to hold the flag indicating whether the old 111 * RDN value(s) should be removed from the target entry for a modify DN 112 * operation. 113 */ 114 public static final String ATTR_DELETE_OLD_RDN = "deleteOldRDN"; 115 116 117 118 /** 119 * The name of the attribute used to hold the new superior DN for a modify DN 120 * operation. 121 */ 122 public static final String ATTR_NEW_SUPERIOR = "newSuperior"; 123 124 125 126 /** 127 * The name of the attribute used to hold information about attributes from a 128 * deleted entry, if available. 129 */ 130 public static final String ATTR_DELETED_ENTRY_ATTRS = "deletedEntryAttrs"; 131 132 133 134 /** 135 * The serial version UID for this serializable class. 136 */ 137 private static final long serialVersionUID = -4018129098468341663L; 138 139 140 141 // Indicates whether to delete the old RDN value(s) in a modify DN operation. 142 private final boolean deleteOldRDN; 143 144 // The change type for this changelog entry. 145 private final ChangeType changeType; 146 147 // A list of the attributes for an add, or the deleted entry attributes for a 148 // delete operation. 149 private final List<Attribute> attributes; 150 151 // A list of the modifications for a modify operation. 152 private final List<Modification> modifications; 153 154 // The change number for the changelog entry. 155 private final long changeNumber; 156 157 // The new RDN for a modify DN operation. 158 private final String newRDN; 159 160 // The new superior DN for a modify DN operation. 161 private final String newSuperior; 162 163 // The DN of the target entry. 164 private final String targetDN; 165 166 167 168 /** 169 * Creates a new changelog entry from the provided entry. 170 * 171 * @param entry The entry from which to create this changelog entry. 172 * 173 * @throws LDAPException If the provided entry cannot be parsed as a 174 * changelog entry. 175 */ 176 public ChangeLogEntry(final Entry entry) 177 throws LDAPException 178 { 179 super(entry); 180 181 182 final Attribute changeNumberAttr = entry.getAttribute(ATTR_CHANGE_NUMBER); 183 if ((changeNumberAttr == null) || (! changeNumberAttr.hasValue())) 184 { 185 throw new LDAPException(ResultCode.DECODING_ERROR, 186 ERR_CHANGELOG_NO_CHANGE_NUMBER.get()); 187 } 188 189 try 190 { 191 changeNumber = Long.parseLong(changeNumberAttr.getValue()); 192 } 193 catch (NumberFormatException nfe) 194 { 195 Debug.debugException(nfe); 196 throw new LDAPException(ResultCode.DECODING_ERROR, 197 ERR_CHANGELOG_INVALID_CHANGE_NUMBER.get(changeNumberAttr.getValue()), 198 nfe); 199 } 200 201 202 final Attribute targetDNAttr = entry.getAttribute(ATTR_TARGET_DN); 203 if ((targetDNAttr == null) || (! targetDNAttr.hasValue())) 204 { 205 throw new LDAPException(ResultCode.DECODING_ERROR, 206 ERR_CHANGELOG_NO_TARGET_DN.get()); 207 } 208 targetDN = targetDNAttr.getValue(); 209 210 211 final Attribute changeTypeAttr = entry.getAttribute(ATTR_CHANGE_TYPE); 212 if ((changeTypeAttr == null) || (! changeTypeAttr.hasValue())) 213 { 214 throw new LDAPException(ResultCode.DECODING_ERROR, 215 ERR_CHANGELOG_NO_CHANGE_TYPE.get()); 216 } 217 changeType = ChangeType.forName(changeTypeAttr.getValue()); 218 if (changeType == null) 219 { 220 throw new LDAPException(ResultCode.DECODING_ERROR, 221 ERR_CHANGELOG_INVALID_CHANGE_TYPE.get(changeTypeAttr.getValue())); 222 } 223 224 225 switch (changeType) 226 { 227 case ADD: 228 attributes = parseAddAttributeList(entry, ATTR_CHANGES, targetDN); 229 modifications = null; 230 newRDN = null; 231 deleteOldRDN = false; 232 newSuperior = null; 233 break; 234 235 case DELETE: 236 attributes = parseDeletedAttributeList(entry, targetDN); 237 modifications = null; 238 newRDN = null; 239 deleteOldRDN = false; 240 newSuperior = null; 241 break; 242 243 case MODIFY: 244 attributes = null; 245 modifications = parseModificationList(entry, targetDN); 246 newRDN = null; 247 deleteOldRDN = false; 248 newSuperior = null; 249 break; 250 251 case MODIFY_DN: 252 attributes = null; 253 modifications = parseModificationList(entry, targetDN); 254 newSuperior = getAttributeValue(ATTR_NEW_SUPERIOR); 255 256 final Attribute newRDNAttr = getAttribute(ATTR_NEW_RDN); 257 if ((newRDNAttr == null) || (! newRDNAttr.hasValue())) 258 { 259 throw new LDAPException(ResultCode.DECODING_ERROR, 260 ERR_CHANGELOG_MISSING_NEW_RDN.get()); 261 } 262 newRDN = newRDNAttr.getValue(); 263 264 final Attribute deleteOldRDNAttr = getAttribute(ATTR_DELETE_OLD_RDN); 265 if ((deleteOldRDNAttr == null) || (! deleteOldRDNAttr.hasValue())) 266 { 267 throw new LDAPException(ResultCode.DECODING_ERROR, 268 ERR_CHANGELOG_MISSING_DELETE_OLD_RDN.get()); 269 } 270 final String delOldRDNStr = toLowerCase(deleteOldRDNAttr.getValue()); 271 if (delOldRDNStr.equals("true")) 272 { 273 deleteOldRDN = true; 274 } 275 else if (delOldRDNStr.equals("false")) 276 { 277 deleteOldRDN = false; 278 } 279 else 280 { 281 throw new LDAPException(ResultCode.DECODING_ERROR, 282 ERR_CHANGELOG_MISSING_DELETE_OLD_RDN.get(delOldRDNStr)); 283 } 284 break; 285 286 default: 287 // This should never happen. 288 throw new LDAPException(ResultCode.DECODING_ERROR, 289 ERR_CHANGELOG_INVALID_CHANGE_TYPE.get(changeTypeAttr.getValue())); 290 } 291 } 292 293 294 295 /** 296 * Constructs a changelog entry from information contained in the provided 297 * LDIF change record. 298 * 299 * @param changeNumber The change number to use for the constructed 300 * changelog entry. 301 * @param changeRecord The LDIF change record with the information to 302 * include in the generated changelog entry. 303 * 304 * @return The changelog entry constructed from the provided change record. 305 * 306 * @throws LDAPException If a problem is encountered while constructing the 307 * changelog entry. 308 */ 309 public static ChangeLogEntry constructChangeLogEntry(final long changeNumber, 310 final LDIFChangeRecord changeRecord) 311 throws LDAPException 312 { 313 final Entry e = 314 new Entry(ATTR_CHANGE_NUMBER + '=' + changeNumber + ",cn=changelog"); 315 e.addAttribute("objectClass", "top", "changeLogEntry"); 316 e.addAttribute(new Attribute(ATTR_CHANGE_NUMBER, 317 IntegerMatchingRule.getInstance(), String.valueOf(changeNumber))); 318 e.addAttribute(new Attribute(ATTR_TARGET_DN, 319 DistinguishedNameMatchingRule.getInstance(), changeRecord.getDN())); 320 e.addAttribute(ATTR_CHANGE_TYPE, changeRecord.getChangeType().getName()); 321 322 switch (changeRecord.getChangeType()) 323 { 324 case ADD: 325 // The changes attribute should be an LDIF-encoded representation of the 326 // attributes from the entry, which is the LDIF representation of the 327 // entry without the first line (which contains the DN). 328 final LDIFAddChangeRecord addRecord = 329 (LDIFAddChangeRecord) changeRecord; 330 final Entry addEntry = new Entry(addRecord.getDN(), 331 addRecord.getAttributes()); 332 final String[] entryLdifLines = addEntry.toLDIF(0); 333 final StringBuilder entryLDIFBuffer = new StringBuilder(); 334 for (int i=1; i < entryLdifLines.length; i++) 335 { 336 entryLDIFBuffer.append(entryLdifLines[i]); 337 entryLDIFBuffer.append(EOL); 338 } 339 e.addAttribute(new Attribute(ATTR_CHANGES, 340 OctetStringMatchingRule.getInstance(), 341 entryLDIFBuffer.toString())); 342 break; 343 344 case DELETE: 345 // No additional information is needed. 346 break; 347 348 case MODIFY: 349 // The changes attribute should be an LDIF-encoded representation of the 350 // modification, with the first two lines (the DN and changetype) 351 // removed. 352 final String[] modLdifLines = changeRecord.toLDIF(0); 353 final StringBuilder modLDIFBuffer = new StringBuilder(); 354 for (int i=2; i < modLdifLines.length; i++) 355 { 356 modLDIFBuffer.append(modLdifLines[i]); 357 modLDIFBuffer.append(EOL); 358 } 359 e.addAttribute(new Attribute(ATTR_CHANGES, 360 OctetStringMatchingRule.getInstance(), modLDIFBuffer.toString())); 361 break; 362 363 case MODIFY_DN: 364 final LDIFModifyDNChangeRecord modDNRecord = 365 (LDIFModifyDNChangeRecord) changeRecord; 366 e.addAttribute(new Attribute(ATTR_NEW_RDN, 367 DistinguishedNameMatchingRule.getInstance(), 368 modDNRecord.getNewRDN())); 369 e.addAttribute(new Attribute(ATTR_DELETE_OLD_RDN, 370 BooleanMatchingRule.getInstance(), 371 (modDNRecord.deleteOldRDN() ? "TRUE" : "FALSE"))); 372 if (modDNRecord.getNewSuperiorDN() != null) 373 { 374 e.addAttribute(new Attribute(ATTR_NEW_SUPERIOR, 375 DistinguishedNameMatchingRule.getInstance(), 376 modDNRecord.getNewSuperiorDN())); 377 } 378 break; 379 } 380 381 return new ChangeLogEntry(e); 382 } 383 384 385 386 /** 387 * Parses the attribute list from the specified attribute in a changelog 388 * entry. 389 * 390 * @param entry The entry containing the data to parse. 391 * @param attrName The name of the attribute from which to parse the 392 * attribute list. 393 * @param targetDN The DN of the target entry. 394 * 395 * @return The parsed attribute list. 396 * 397 * @throws LDAPException If an error occurs while parsing the attribute 398 * list. 399 */ 400 protected static List<Attribute> parseAddAttributeList(final Entry entry, 401 final String attrName, 402 final String targetDN) 403 throws LDAPException 404 { 405 final Attribute changesAttr = entry.getAttribute(attrName); 406 if ((changesAttr == null) || (! changesAttr.hasValue())) 407 { 408 throw new LDAPException(ResultCode.DECODING_ERROR, 409 ERR_CHANGELOG_MISSING_CHANGES.get()); 410 } 411 412 final ArrayList<String> ldifLines = new ArrayList<String>(); 413 ldifLines.add("dn: " + targetDN); 414 415 final StringTokenizer tokenizer = 416 new StringTokenizer(changesAttr.getValue(), "\r\n"); 417 while (tokenizer.hasMoreTokens()) 418 { 419 ldifLines.add(tokenizer.nextToken()); 420 } 421 422 final String[] lineArray = new String[ldifLines.size()]; 423 ldifLines.toArray(lineArray); 424 425 try 426 { 427 final Entry e = LDIFReader.decodeEntry(lineArray); 428 return Collections.unmodifiableList( 429 new ArrayList<Attribute>(e.getAttributes())); 430 } 431 catch (LDIFException le) 432 { 433 Debug.debugException(le); 434 throw new LDAPException(ResultCode.DECODING_ERROR, 435 ERR_CHANGELOG_CANNOT_PARSE_ATTR_LIST.get(attrName, 436 getExceptionMessage(le)), 437 le); 438 } 439 } 440 441 442 443 /** 444 * Parses the list of deleted attributes from a changelog entry representing a 445 * delete operation. The attribute is optional, so it may not be present at 446 * all, and there are two different encodings that we need to handle. One 447 * encoding is the same as is used for the add attribute list, and the second 448 * is similar to the encoding used for the list of changes, except that it 449 * ends with a NULL byte (0x00). 450 * 451 * @param entry The entry containing the data to parse. 452 * @param targetDN The DN of the target entry. 453 * 454 * @return The parsed deleted attribute list, or {@code null} if the 455 * changelog entry does not include a deleted attribute list. 456 * 457 * @throws LDAPException If an error occurs while parsing the deleted 458 * attribute list. 459 */ 460 private static List<Attribute> parseDeletedAttributeList(final Entry entry, 461 final String targetDN) 462 throws LDAPException 463 { 464 final Attribute deletedEntryAttrs = 465 entry.getAttribute(ATTR_DELETED_ENTRY_ATTRS); 466 if ((deletedEntryAttrs == null) || (! deletedEntryAttrs.hasValue())) 467 { 468 return null; 469 } 470 471 final byte[] valueBytes = deletedEntryAttrs.getValueByteArray(); 472 if ((valueBytes.length > 0) && (valueBytes[valueBytes.length-1] == 0x00)) 473 { 474 final String valueStr = new String(valueBytes, 0, valueBytes.length-2); 475 476 final ArrayList<String> ldifLines = new ArrayList<String>(); 477 ldifLines.add("dn: " + targetDN); 478 ldifLines.add("changetype: modify"); 479 480 final StringTokenizer tokenizer = new StringTokenizer(valueStr, "\r\n"); 481 while (tokenizer.hasMoreTokens()) 482 { 483 ldifLines.add(tokenizer.nextToken()); 484 } 485 486 final String[] lineArray = new String[ldifLines.size()]; 487 ldifLines.toArray(lineArray); 488 489 try 490 { 491 492 final LDIFModifyChangeRecord changeRecord = 493 (LDIFModifyChangeRecord) LDIFReader.decodeChangeRecord(lineArray); 494 final Modification[] mods = changeRecord.getModifications(); 495 final ArrayList<Attribute> attrs = 496 new ArrayList<Attribute>(mods.length); 497 for (final Modification m : mods) 498 { 499 if (! m.getModificationType().equals(ModificationType.DELETE)) 500 { 501 throw new LDAPException(ResultCode.DECODING_ERROR, 502 ERR_CHANGELOG_INVALID_DELENTRYATTRS_MOD_TYPE.get( 503 ATTR_DELETED_ENTRY_ATTRS)); 504 } 505 506 attrs.add(m.getAttribute()); 507 } 508 509 return Collections.unmodifiableList(attrs); 510 } 511 catch (LDIFException le) 512 { 513 Debug.debugException(le); 514 throw new LDAPException(ResultCode.DECODING_ERROR, 515 ERR_CHANGELOG_INVALID_DELENTRYATTRS_MODS.get( 516 ATTR_DELETED_ENTRY_ATTRS, getExceptionMessage(le)), le); 517 } 518 } 519 else 520 { 521 final ArrayList<String> ldifLines = new ArrayList<String>(); 522 ldifLines.add("dn: " + targetDN); 523 524 final StringTokenizer tokenizer = 525 new StringTokenizer(deletedEntryAttrs.getValue(), "\r\n"); 526 while (tokenizer.hasMoreTokens()) 527 { 528 ldifLines.add(tokenizer.nextToken()); 529 } 530 531 final String[] lineArray = new String[ldifLines.size()]; 532 ldifLines.toArray(lineArray); 533 534 try 535 { 536 final Entry e = LDIFReader.decodeEntry(lineArray); 537 return Collections.unmodifiableList( 538 new ArrayList<Attribute>(e.getAttributes())); 539 } 540 catch (LDIFException le) 541 { 542 Debug.debugException(le); 543 throw new LDAPException(ResultCode.DECODING_ERROR, 544 ERR_CHANGELOG_CANNOT_PARSE_DELENTRYATTRS.get( 545 ATTR_DELETED_ENTRY_ATTRS, getExceptionMessage(le)), le); 546 } 547 } 548 } 549 550 551 552 /** 553 * Parses the modification list from a changelog entry representing a modify 554 * operation. 555 * 556 * @param entry The entry containing the data to parse. 557 * @param targetDN The DN of the target entry. 558 * 559 * @return The parsed modification list, or {@code null} if the changelog 560 * entry does not include any modifications. 561 * 562 * @throws LDAPException If an error occurs while parsing the modification 563 * list. 564 */ 565 private static List<Modification> parseModificationList(final Entry entry, 566 final String targetDN) 567 throws LDAPException 568 { 569 final Attribute changesAttr = entry.getAttribute(ATTR_CHANGES); 570 if ((changesAttr == null) || (! changesAttr.hasValue())) 571 { 572 return null; 573 } 574 575 final byte[] valueBytes = changesAttr.getValueByteArray(); 576 if (valueBytes.length == 0) 577 { 578 return null; 579 } 580 581 582 final ArrayList<String> ldifLines = new ArrayList<String>(); 583 ldifLines.add("dn: " + targetDN); 584 ldifLines.add("changetype: modify"); 585 586 // Even though it's a violation of the specification in 587 // draft-good-ldap-changelog, it appears that some servers (e.g., Sun DSEE) 588 // may terminate the changes value with a null character (\u0000). If that 589 // is the case, then we'll need to strip it off before trying to parse it. 590 final StringTokenizer tokenizer; 591 if ((valueBytes.length > 0) && (valueBytes[valueBytes.length-1] == 0x00)) 592 { 593 final String fullValue = changesAttr.getValue(); 594 final String realValue = fullValue.substring(0, fullValue.length()-2); 595 tokenizer = new StringTokenizer(realValue, "\r\n"); 596 } 597 else 598 { 599 tokenizer = new StringTokenizer(changesAttr.getValue(), "\r\n"); 600 } 601 602 while (tokenizer.hasMoreTokens()) 603 { 604 ldifLines.add(tokenizer.nextToken()); 605 } 606 607 final String[] lineArray = new String[ldifLines.size()]; 608 ldifLines.toArray(lineArray); 609 610 try 611 { 612 final LDIFModifyChangeRecord changeRecord = 613 (LDIFModifyChangeRecord) LDIFReader.decodeChangeRecord(lineArray); 614 return Collections.unmodifiableList( 615 Arrays.asList(changeRecord.getModifications())); 616 } 617 catch (LDIFException le) 618 { 619 Debug.debugException(le); 620 throw new LDAPException(ResultCode.DECODING_ERROR, 621 ERR_CHANGELOG_CANNOT_PARSE_MOD_LIST.get(ATTR_CHANGES, 622 getExceptionMessage(le)), 623 le); 624 } 625 } 626 627 628 629 /** 630 * Retrieves the change number for this changelog entry. 631 * 632 * @return The change number for this changelog entry. 633 */ 634 public final long getChangeNumber() 635 { 636 return changeNumber; 637 } 638 639 640 641 /** 642 * Retrieves the target DN for this changelog entry. 643 * 644 * @return The target DN for this changelog entry. 645 */ 646 public final String getTargetDN() 647 { 648 return targetDN; 649 } 650 651 652 653 /** 654 * Retrieves the change type for this changelog entry. 655 * 656 * @return The change type for this changelog entry. 657 */ 658 public final ChangeType getChangeType() 659 { 660 return changeType; 661 } 662 663 664 665 /** 666 * Retrieves the attribute list for an add changelog entry. 667 * 668 * @return The attribute list for an add changelog entry, or {@code null} if 669 * this changelog entry does not represent an add operation. 670 */ 671 public final List<Attribute> getAddAttributes() 672 { 673 if (changeType == ChangeType.ADD) 674 { 675 return attributes; 676 } 677 else 678 { 679 return null; 680 } 681 } 682 683 684 685 /** 686 * Retrieves the list of deleted entry attributes for a delete changelog 687 * entry. Note that this is a non-standard extension implemented by some 688 * types of servers and is not defined in draft-good-ldap-changelog and may 689 * not be provided by some servers. 690 * 691 * @return The delete entry attribute list for a delete changelog entry, or 692 * {@code null} if this changelog entry does not represent a delete 693 * operation or no deleted entry attributes were included in the 694 * changelog entry. 695 */ 696 public final List<Attribute> getDeletedEntryAttributes() 697 { 698 if (changeType == ChangeType.DELETE) 699 { 700 return attributes; 701 } 702 else 703 { 704 return null; 705 } 706 } 707 708 709 710 /** 711 * Retrieves the list of modifications for a modify changelog entry. Note 712 * some directory servers may also include changes for modify DN change 713 * records if there were updates to operational attributes (e.g., 714 * modifiersName and modifyTimestamp). 715 * 716 * @return The list of modifications for a modify (or possibly modify DN) 717 * changelog entry, or {@code null} if this changelog entry does 718 * not represent a modify operation or a modify DN operation with 719 * additional changes. 720 */ 721 public final List<Modification> getModifications() 722 { 723 return modifications; 724 } 725 726 727 728 /** 729 * Retrieves the new RDN for a modify DN changelog entry. 730 * 731 * @return The new RDN for a modify DN changelog entry, or {@code null} if 732 * this changelog entry does not represent a modify DN operation. 733 */ 734 public final String getNewRDN() 735 { 736 return newRDN; 737 } 738 739 740 741 /** 742 * Indicates whether the old RDN value(s) should be removed from the entry 743 * targeted by this modify DN changelog entry. 744 * 745 * @return {@code true} if the old RDN value(s) should be removed from the 746 * entry, or {@code false} if not or if this changelog entry does not 747 * represent a modify DN operation. 748 */ 749 public final boolean deleteOldRDN() 750 { 751 return deleteOldRDN; 752 } 753 754 755 756 /** 757 * Retrieves the new superior DN for a modify DN changelog entry. 758 * 759 * @return The new superior DN for a modify DN changelog entry, or 760 * {@code null} if there is no new superior DN, or if this changelog 761 * entry does not represent a modify DN operation. 762 */ 763 public final String getNewSuperior() 764 { 765 return newSuperior; 766 } 767 768 769 770 /** 771 * Retrieves the DN of the entry after the change has been processed. For an 772 * add or modify operation, the new DN will be the same as the target DN. For 773 * a modify DN operation, the new DN will be constructed from the original DN, 774 * the new RDN, and the new superior DN. For a delete operation, it will be 775 * {@code null} because the entry will no longer exist. 776 * 777 * @return The DN of the entry after the change has been processed, or 778 * {@code null} if the entry no longer exists. 779 */ 780 public final String getNewDN() 781 { 782 switch (changeType) 783 { 784 case ADD: 785 case MODIFY: 786 return targetDN; 787 788 case MODIFY_DN: 789 // This will be handled below. 790 break; 791 792 case DELETE: 793 default: 794 return null; 795 } 796 797 try 798 { 799 final RDN parsedNewRDN = new RDN(newRDN); 800 801 if (newSuperior == null) 802 { 803 final DN parsedTargetDN = new DN(targetDN); 804 final DN parentDN = parsedTargetDN.getParent(); 805 if (parentDN == null) 806 { 807 return new DN(parsedNewRDN).toString(); 808 } 809 else 810 { 811 return new DN(parsedNewRDN, parentDN).toString(); 812 } 813 } 814 else 815 { 816 final DN parsedNewSuperior = new DN(newSuperior); 817 return new DN(parsedNewRDN, parsedNewSuperior).toString(); 818 } 819 } 820 catch (final Exception e) 821 { 822 // This should never happen. 823 Debug.debugException(e); 824 return null; 825 } 826 } 827 828 829 830 /** 831 * Retrieves an LDIF change record that is analogous to the operation 832 * represented by this changelog entry. 833 * 834 * @return An LDIF change record that is analogous to the operation 835 * represented by this changelog entry. 836 */ 837 public final LDIFChangeRecord toLDIFChangeRecord() 838 { 839 switch (changeType) 840 { 841 case ADD: 842 return new LDIFAddChangeRecord(targetDN, attributes); 843 844 case DELETE: 845 return new LDIFDeleteChangeRecord(targetDN); 846 847 case MODIFY: 848 return new LDIFModifyChangeRecord(targetDN, modifications); 849 850 case MODIFY_DN: 851 return new LDIFModifyDNChangeRecord(targetDN, newRDN, deleteOldRDN, 852 newSuperior); 853 854 default: 855 // This should never happen. 856 return null; 857 } 858 } 859 860 861 862 /** 863 * Processes the operation represented by this changelog entry using the 864 * provided LDAP connection. 865 * 866 * @param connection The connection (or connection pool) to use to process 867 * the operation. 868 * 869 * @return The result of processing the operation. 870 * 871 * @throws LDAPException If the operation could not be processed 872 * successfully. 873 */ 874 public final LDAPResult processChange(final LDAPInterface connection) 875 throws LDAPException 876 { 877 switch (changeType) 878 { 879 case ADD: 880 return connection.add(targetDN, attributes); 881 882 case DELETE: 883 return connection.delete(targetDN); 884 885 case MODIFY: 886 return connection.modify(targetDN, modifications); 887 888 case MODIFY_DN: 889 return connection.modifyDN(targetDN, newRDN, deleteOldRDN, newSuperior); 890 891 default: 892 // This should never happen. 893 return null; 894 } 895 } 896}