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