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