001/* 002 * Copyright 2007-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2019 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.StaticUtils; 048import com.unboundid.util.ThreadSafety; 049import com.unboundid.util.ThreadSafetyLevel; 050 051import static com.unboundid.ldap.sdk.LDAPMessages.*; 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 = 273 StaticUtils.toLowerCase(deleteOldRDNAttr.getValue()); 274 if (delOldRDNStr.equals("true")) 275 { 276 deleteOldRDN = true; 277 } 278 else if (delOldRDNStr.equals("false")) 279 { 280 deleteOldRDN = false; 281 } 282 else 283 { 284 throw new LDAPException(ResultCode.DECODING_ERROR, 285 ERR_CHANGELOG_MISSING_DELETE_OLD_RDN.get(delOldRDNStr)); 286 } 287 break; 288 289 default: 290 // This should never happen. 291 throw new LDAPException(ResultCode.DECODING_ERROR, 292 ERR_CHANGELOG_INVALID_CHANGE_TYPE.get(changeTypeAttr.getValue())); 293 } 294 } 295 296 297 298 /** 299 * Constructs a changelog entry from information contained in the provided 300 * LDIF change record. 301 * 302 * @param changeNumber The change number to use for the constructed 303 * changelog entry. 304 * @param changeRecord The LDIF change record with the information to 305 * include in the generated changelog entry. 306 * 307 * @return The changelog entry constructed from the provided change record. 308 * 309 * @throws LDAPException If a problem is encountered while constructing the 310 * changelog entry. 311 */ 312 public static ChangeLogEntry constructChangeLogEntry(final long changeNumber, 313 final LDIFChangeRecord changeRecord) 314 throws LDAPException 315 { 316 final Entry e = 317 new Entry(ATTR_CHANGE_NUMBER + '=' + changeNumber + ",cn=changelog"); 318 e.addAttribute("objectClass", "top", "changeLogEntry"); 319 e.addAttribute(new Attribute(ATTR_CHANGE_NUMBER, 320 IntegerMatchingRule.getInstance(), String.valueOf(changeNumber))); 321 e.addAttribute(new Attribute(ATTR_TARGET_DN, 322 DistinguishedNameMatchingRule.getInstance(), changeRecord.getDN())); 323 e.addAttribute(ATTR_CHANGE_TYPE, changeRecord.getChangeType().getName()); 324 325 switch (changeRecord.getChangeType()) 326 { 327 case ADD: 328 // The changes attribute should be an LDIF-encoded representation of the 329 // attributes from the entry, which is the LDIF representation of the 330 // entry without the first line (which contains the DN). 331 final LDIFAddChangeRecord addRecord = 332 (LDIFAddChangeRecord) changeRecord; 333 final Entry addEntry = new Entry(addRecord.getDN(), 334 addRecord.getAttributes()); 335 final String[] entryLdifLines = addEntry.toLDIF(0); 336 final StringBuilder entryLDIFBuffer = new StringBuilder(); 337 for (int i=1; i < entryLdifLines.length; i++) 338 { 339 entryLDIFBuffer.append(entryLdifLines[i]); 340 entryLDIFBuffer.append(StaticUtils.EOL); 341 } 342 e.addAttribute(new Attribute(ATTR_CHANGES, 343 OctetStringMatchingRule.getInstance(), 344 entryLDIFBuffer.toString())); 345 break; 346 347 case DELETE: 348 // No additional information is needed. 349 break; 350 351 case MODIFY: 352 // The changes attribute should be an LDIF-encoded representation of the 353 // modification, with the first two lines (the DN and changetype) 354 // removed. 355 final String[] modLdifLines = changeRecord.toLDIF(0); 356 final StringBuilder modLDIFBuffer = new StringBuilder(); 357 for (int i=2; i < modLdifLines.length; i++) 358 { 359 modLDIFBuffer.append(modLdifLines[i]); 360 modLDIFBuffer.append(StaticUtils.EOL); 361 } 362 e.addAttribute(new Attribute(ATTR_CHANGES, 363 OctetStringMatchingRule.getInstance(), modLDIFBuffer.toString())); 364 break; 365 366 case MODIFY_DN: 367 final LDIFModifyDNChangeRecord modDNRecord = 368 (LDIFModifyDNChangeRecord) changeRecord; 369 e.addAttribute(new Attribute(ATTR_NEW_RDN, 370 DistinguishedNameMatchingRule.getInstance(), 371 modDNRecord.getNewRDN())); 372 e.addAttribute(new Attribute(ATTR_DELETE_OLD_RDN, 373 BooleanMatchingRule.getInstance(), 374 (modDNRecord.deleteOldRDN() ? "TRUE" : "FALSE"))); 375 if (modDNRecord.getNewSuperiorDN() != null) 376 { 377 e.addAttribute(new Attribute(ATTR_NEW_SUPERIOR, 378 DistinguishedNameMatchingRule.getInstance(), 379 modDNRecord.getNewSuperiorDN())); 380 } 381 break; 382 } 383 384 return new ChangeLogEntry(e); 385 } 386 387 388 389 /** 390 * Parses the attribute list from the specified attribute in a changelog 391 * entry. 392 * 393 * @param entry The entry containing the data to parse. 394 * @param attrName The name of the attribute from which to parse the 395 * attribute list. 396 * @param targetDN The DN of the target entry. 397 * 398 * @return The parsed attribute list. 399 * 400 * @throws LDAPException If an error occurs while parsing the attribute 401 * list. 402 */ 403 protected static List<Attribute> parseAddAttributeList(final Entry entry, 404 final String attrName, 405 final String targetDN) 406 throws LDAPException 407 { 408 final Attribute changesAttr = entry.getAttribute(attrName); 409 if ((changesAttr == null) || (! changesAttr.hasValue())) 410 { 411 throw new LDAPException(ResultCode.DECODING_ERROR, 412 ERR_CHANGELOG_MISSING_CHANGES.get()); 413 } 414 415 final ArrayList<String> ldifLines = new ArrayList<>(20); 416 ldifLines.add("dn: " + targetDN); 417 418 final StringTokenizer tokenizer = 419 new StringTokenizer(changesAttr.getValue(), "\r\n"); 420 while (tokenizer.hasMoreTokens()) 421 { 422 ldifLines.add(tokenizer.nextToken()); 423 } 424 425 final String[] lineArray = new String[ldifLines.size()]; 426 ldifLines.toArray(lineArray); 427 428 try 429 { 430 final Entry e = LDIFReader.decodeEntry(true, TrailingSpaceBehavior.RETAIN, 431 null, lineArray); 432 return Collections.unmodifiableList(new ArrayList<>(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 StaticUtils.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<>(20); 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 = new ArrayList<>(mods.length); 500 for (final Modification m : mods) 501 { 502 if (! m.getModificationType().equals(ModificationType.DELETE)) 503 { 504 throw new LDAPException(ResultCode.DECODING_ERROR, 505 ERR_CHANGELOG_INVALID_DELENTRYATTRS_MOD_TYPE.get( 506 ATTR_DELETED_ENTRY_ATTRS)); 507 } 508 509 attrs.add(m.getAttribute()); 510 } 511 512 return Collections.unmodifiableList(attrs); 513 } 514 catch (final LDIFException le) 515 { 516 Debug.debugException(le); 517 throw new LDAPException(ResultCode.DECODING_ERROR, 518 ERR_CHANGELOG_INVALID_DELENTRYATTRS_MODS.get( 519 ATTR_DELETED_ENTRY_ATTRS, 520 StaticUtils.getExceptionMessage(le)), 521 le); 522 } 523 } 524 else 525 { 526 final ArrayList<String> ldifLines = new ArrayList<>(20); 527 ldifLines.add("dn: " + targetDN); 528 529 final StringTokenizer tokenizer = 530 new StringTokenizer(deletedEntryAttrs.getValue(), "\r\n"); 531 while (tokenizer.hasMoreTokens()) 532 { 533 ldifLines.add(tokenizer.nextToken()); 534 } 535 536 final String[] lineArray = new String[ldifLines.size()]; 537 ldifLines.toArray(lineArray); 538 539 try 540 { 541 final Entry e = LDIFReader.decodeEntry(true, 542 TrailingSpaceBehavior.RETAIN, null, lineArray); 543 return Collections.unmodifiableList(new ArrayList<>(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, 551 StaticUtils.getExceptionMessage(le)), 552 le); 553 } 554 } 555 } 556 557 558 559 /** 560 * Parses the modification list from a changelog entry representing a modify 561 * operation. 562 * 563 * @param entry The entry containing the data to parse. 564 * @param targetDN The DN of the target entry. 565 * 566 * @return The parsed modification list, or {@code null} if the changelog 567 * entry does not include any modifications. 568 * 569 * @throws LDAPException If an error occurs while parsing the modification 570 * list. 571 */ 572 private static List<Modification> parseModificationList(final Entry entry, 573 final String targetDN) 574 throws LDAPException 575 { 576 final Attribute changesAttr = entry.getAttribute(ATTR_CHANGES); 577 if ((changesAttr == null) || (! changesAttr.hasValue())) 578 { 579 return null; 580 } 581 582 final byte[] valueBytes = changesAttr.getValueByteArray(); 583 if (valueBytes.length == 0) 584 { 585 return null; 586 } 587 588 589 final ArrayList<String> ldifLines = new ArrayList<>(20); 590 ldifLines.add("dn: " + targetDN); 591 ldifLines.add("changetype: modify"); 592 593 // Even though it's a violation of the specification in 594 // draft-good-ldap-changelog, it appears that some servers (e.g., Sun DSEE) 595 // may terminate the changes value with a null character (\u0000). If that 596 // is the case, then we'll need to strip it off before trying to parse it. 597 final StringTokenizer tokenizer; 598 if ((valueBytes.length > 0) && (valueBytes[valueBytes.length-1] == 0x00)) 599 { 600 final String fullValue = changesAttr.getValue(); 601 final String realValue = fullValue.substring(0, fullValue.length()-2); 602 tokenizer = new StringTokenizer(realValue, "\r\n"); 603 } 604 else 605 { 606 tokenizer = new StringTokenizer(changesAttr.getValue(), "\r\n"); 607 } 608 609 while (tokenizer.hasMoreTokens()) 610 { 611 ldifLines.add(tokenizer.nextToken()); 612 } 613 614 final String[] lineArray = new String[ldifLines.size()]; 615 ldifLines.toArray(lineArray); 616 617 try 618 { 619 final LDIFModifyChangeRecord changeRecord = 620 (LDIFModifyChangeRecord) LDIFReader.decodeChangeRecord(lineArray); 621 return Collections.unmodifiableList( 622 Arrays.asList(changeRecord.getModifications())); 623 } 624 catch (final LDIFException le) 625 { 626 Debug.debugException(le); 627 throw new LDAPException(ResultCode.DECODING_ERROR, 628 ERR_CHANGELOG_CANNOT_PARSE_MOD_LIST.get(ATTR_CHANGES, 629 StaticUtils.getExceptionMessage(le)), 630 le); 631 } 632 } 633 634 635 636 /** 637 * Retrieves the change number for this changelog entry. 638 * 639 * @return The change number for this changelog entry. 640 */ 641 public final long getChangeNumber() 642 { 643 return changeNumber; 644 } 645 646 647 648 /** 649 * Retrieves the target DN for this changelog entry. 650 * 651 * @return The target DN for this changelog entry. 652 */ 653 public final String getTargetDN() 654 { 655 return targetDN; 656 } 657 658 659 660 /** 661 * Retrieves the change type for this changelog entry. 662 * 663 * @return The change type for this changelog entry. 664 */ 665 public final ChangeType getChangeType() 666 { 667 return changeType; 668 } 669 670 671 672 /** 673 * Retrieves the attribute list for an add changelog entry. 674 * 675 * @return The attribute list for an add changelog entry, or {@code null} if 676 * this changelog entry does not represent an add operation. 677 */ 678 public final List<Attribute> getAddAttributes() 679 { 680 if (changeType == ChangeType.ADD) 681 { 682 return attributes; 683 } 684 else 685 { 686 return null; 687 } 688 } 689 690 691 692 /** 693 * Retrieves the list of deleted entry attributes for a delete changelog 694 * entry. Note that this is a non-standard extension implemented by some 695 * types of servers and is not defined in draft-good-ldap-changelog and may 696 * not be provided by some servers. 697 * 698 * @return The delete entry attribute list for a delete changelog entry, or 699 * {@code null} if this changelog entry does not represent a delete 700 * operation or no deleted entry attributes were included in the 701 * changelog entry. 702 */ 703 public final List<Attribute> getDeletedEntryAttributes() 704 { 705 if (changeType == ChangeType.DELETE) 706 { 707 return attributes; 708 } 709 else 710 { 711 return null; 712 } 713 } 714 715 716 717 /** 718 * Retrieves the list of modifications for a modify changelog entry. Note 719 * some directory servers may also include changes for modify DN change 720 * records if there were updates to operational attributes (e.g., 721 * modifiersName and modifyTimestamp). 722 * 723 * @return The list of modifications for a modify (or possibly modify DN) 724 * changelog entry, or {@code null} if this changelog entry does 725 * not represent a modify operation or a modify DN operation with 726 * additional changes. 727 */ 728 public final List<Modification> getModifications() 729 { 730 return modifications; 731 } 732 733 734 735 /** 736 * Retrieves the new RDN for a modify DN changelog entry. 737 * 738 * @return The new RDN for a modify DN changelog entry, or {@code null} if 739 * this changelog entry does not represent a modify DN operation. 740 */ 741 public final String getNewRDN() 742 { 743 return newRDN; 744 } 745 746 747 748 /** 749 * Indicates whether the old RDN value(s) should be removed from the entry 750 * targeted by this modify DN changelog entry. 751 * 752 * @return {@code true} if the old RDN value(s) should be removed from the 753 * entry, or {@code false} if not or if this changelog entry does not 754 * represent a modify DN operation. 755 */ 756 public final boolean deleteOldRDN() 757 { 758 return deleteOldRDN; 759 } 760 761 762 763 /** 764 * Retrieves the new superior DN for a modify DN changelog entry. 765 * 766 * @return The new superior DN for a modify DN changelog entry, or 767 * {@code null} if there is no new superior DN, or if this changelog 768 * entry does not represent a modify DN operation. 769 */ 770 public final String getNewSuperior() 771 { 772 return newSuperior; 773 } 774 775 776 777 /** 778 * Retrieves the DN of the entry after the change has been processed. For an 779 * add or modify operation, the new DN will be the same as the target DN. For 780 * a modify DN operation, the new DN will be constructed from the original DN, 781 * the new RDN, and the new superior DN. For a delete operation, it will be 782 * {@code null} because the entry will no longer exist. 783 * 784 * @return The DN of the entry after the change has been processed, or 785 * {@code null} if the entry no longer exists. 786 */ 787 public final String getNewDN() 788 { 789 switch (changeType) 790 { 791 case ADD: 792 case MODIFY: 793 return targetDN; 794 795 case MODIFY_DN: 796 // This will be handled below. 797 break; 798 799 case DELETE: 800 default: 801 return null; 802 } 803 804 try 805 { 806 final RDN parsedNewRDN = new RDN(newRDN); 807 808 if (newSuperior == null) 809 { 810 final DN parsedTargetDN = new DN(targetDN); 811 final DN parentDN = parsedTargetDN.getParent(); 812 if (parentDN == null) 813 { 814 return new DN(parsedNewRDN).toString(); 815 } 816 else 817 { 818 return new DN(parsedNewRDN, parentDN).toString(); 819 } 820 } 821 else 822 { 823 final DN parsedNewSuperior = new DN(newSuperior); 824 return new DN(parsedNewRDN, parsedNewSuperior).toString(); 825 } 826 } 827 catch (final Exception e) 828 { 829 // This should never happen. 830 Debug.debugException(e); 831 return null; 832 } 833 } 834 835 836 837 /** 838 * Retrieves an LDIF change record that is analogous to the operation 839 * represented by this changelog entry. 840 * 841 * @return An LDIF change record that is analogous to the operation 842 * represented by this changelog entry. 843 */ 844 public final LDIFChangeRecord toLDIFChangeRecord() 845 { 846 switch (changeType) 847 { 848 case ADD: 849 return new LDIFAddChangeRecord(targetDN, attributes); 850 851 case DELETE: 852 return new LDIFDeleteChangeRecord(targetDN); 853 854 case MODIFY: 855 return new LDIFModifyChangeRecord(targetDN, modifications); 856 857 case MODIFY_DN: 858 return new LDIFModifyDNChangeRecord(targetDN, newRDN, deleteOldRDN, 859 newSuperior); 860 861 default: 862 // This should never happen. 863 return null; 864 } 865 } 866 867 868 869 /** 870 * Processes the operation represented by this changelog entry using the 871 * provided LDAP connection. 872 * 873 * @param connection The connection (or connection pool) to use to process 874 * the operation. 875 * 876 * @return The result of processing the operation. 877 * 878 * @throws LDAPException If the operation could not be processed 879 * successfully. 880 */ 881 public final LDAPResult processChange(final LDAPInterface connection) 882 throws LDAPException 883 { 884 switch (changeType) 885 { 886 case ADD: 887 return connection.add(targetDN, attributes); 888 889 case DELETE: 890 return connection.delete(targetDN); 891 892 case MODIFY: 893 return connection.modify(targetDN, modifications); 894 895 case MODIFY_DN: 896 return connection.modifyDN(targetDN, newRDN, deleteOldRDN, newSuperior); 897 898 default: 899 // This should never happen. 900 return null; 901 } 902 } 903}