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