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