001 /*
002 * Copyright 2007-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2016 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 */
021 package com.unboundid.ldap.sdk;
022
023
024
025 import java.util.ArrayList;
026 import java.util.Arrays;
027 import java.util.Collections;
028 import java.util.List;
029 import java.util.StringTokenizer;
030
031 import com.unboundid.ldif.LDIFAddChangeRecord;
032 import com.unboundid.ldif.LDIFChangeRecord;
033 import com.unboundid.ldif.LDIFDeleteChangeRecord;
034 import com.unboundid.ldif.LDIFException;
035 import com.unboundid.ldif.LDIFModifyChangeRecord;
036 import com.unboundid.ldif.LDIFModifyDNChangeRecord;
037 import com.unboundid.ldif.LDIFReader;
038 import com.unboundid.ldif.TrailingSpaceBehavior;
039 import com.unboundid.ldap.matchingrules.BooleanMatchingRule;
040 import com.unboundid.ldap.matchingrules.DistinguishedNameMatchingRule;
041 import com.unboundid.ldap.matchingrules.IntegerMatchingRule;
042 import com.unboundid.ldap.matchingrules.OctetStringMatchingRule;
043 import com.unboundid.util.Debug;
044 import com.unboundid.util.NotExtensible;
045 import com.unboundid.util.NotMutable;
046 import com.unboundid.util.ThreadSafety;
047 import com.unboundid.util.ThreadSafetyLevel;
048
049 import static com.unboundid.ldap.sdk.LDAPMessages.*;
050 import 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)
065 public 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 }