001    /*
002     * Copyright 2007-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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    }