001/*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 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.ldif.TrailingSpaceBehavior;
039import com.unboundid.ldap.matchingrules.BooleanMatchingRule;
040import com.unboundid.ldap.matchingrules.DistinguishedNameMatchingRule;
041import com.unboundid.ldap.matchingrules.IntegerMatchingRule;
042import com.unboundid.ldap.matchingrules.OctetStringMatchingRule;
043import com.unboundid.util.Debug;
044import com.unboundid.util.NotExtensible;
045import com.unboundid.util.NotMutable;
046import com.unboundid.util.ThreadSafety;
047import com.unboundid.util.ThreadSafetyLevel;
048
049import static com.unboundid.ldap.sdk.LDAPMessages.*;
050import 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)
065public 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}