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