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