001/*
002 * Copyright 2007-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2014 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collections;
028import java.util.List;
029import java.util.StringTokenizer;
030
031import com.unboundid.ldif.LDIFAddChangeRecord;
032import com.unboundid.ldif.LDIFChangeRecord;
033import com.unboundid.ldif.LDIFDeleteChangeRecord;
034import com.unboundid.ldif.LDIFException;
035import com.unboundid.ldif.LDIFModifyChangeRecord;
036import com.unboundid.ldif.LDIFModifyDNChangeRecord;
037import com.unboundid.ldif.LDIFReader;
038import com.unboundid.ldap.matchingrules.BooleanMatchingRule;
039import com.unboundid.ldap.matchingrules.DistinguishedNameMatchingRule;
040import com.unboundid.ldap.matchingrules.IntegerMatchingRule;
041import com.unboundid.ldap.matchingrules.OctetStringMatchingRule;
042import com.unboundid.util.Debug;
043import com.unboundid.util.NotExtensible;
044import com.unboundid.util.NotMutable;
045import com.unboundid.util.ThreadSafety;
046import com.unboundid.util.ThreadSafetyLevel;
047
048import static com.unboundid.ldap.sdk.LDAPMessages.*;
049import 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)
064public 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}