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.ldif;
022    
023    
024    
025    import java.util.Collections;
026    import java.util.List;
027    import java.util.StringTokenizer;
028    
029    import com.unboundid.asn1.ASN1OctetString;
030    import com.unboundid.ldap.sdk.ChangeType;
031    import com.unboundid.ldap.sdk.Control;
032    import com.unboundid.ldap.sdk.DN;
033    import com.unboundid.ldap.sdk.Entry;
034    import com.unboundid.ldap.sdk.LDAPException;
035    import com.unboundid.ldap.sdk.LDAPInterface;
036    import com.unboundid.ldap.sdk.LDAPResult;
037    import com.unboundid.util.ByteStringBuffer;
038    
039    import static com.unboundid.util.Validator.*;
040    
041    
042    
043    /**
044     * This class provides a base class for LDIF change records, which can be used
045     * to represent add, delete, modify, and modify DN operations in LDIF form.
046     * <BR><BR>
047     * <H2>Example</H2>
048     * The following example iterates through all of the change records contained in
049     * an LDIF file and attempts to apply those changes to a directory server:
050     * <PRE>
051     * LDIFReader ldifReader = new LDIFReader(pathToLDIFFile);
052     *
053     * int changesRead = 0;
054     * int changesProcessed = 0;
055     * int errorsEncountered = 0;
056     * while (true)
057     * {
058     *   LDIFChangeRecord changeRecord;
059     *   try
060     *   {
061     *     changeRecord = ldifReader.readChangeRecord();
062     *     if (changeRecord == null)
063     *     {
064     *       // All changes have been processed.
065     *       break;
066     *     }
067     *
068     *     changesRead++;
069     *   }
070     *   catch (LDIFException le)
071     *   {
072     *     errorsEncountered++;
073     *     if (le.mayContinueReading())
074     *     {
075     *       // A recoverable error occurred while attempting to read a change
076     *       // record, at or near line number le.getLineNumber()
077     *       // The change record will be skipped, but we'll try to keep reading
078     *       // from the LDIF file.
079     *       continue;
080     *     }
081     *     else
082     *     {
083     *       // An unrecoverable error occurred while attempting to read a change
084     *       // record, at or near line number le.getLineNumber()
085     *       // No further LDIF processing will be performed.
086     *       break;
087     *     }
088     *   }
089     *   catch (IOException ioe)
090     *   {
091     *     // An I/O error occurred while attempting to read from the LDIF file.
092     *     // No further LDIF processing will be performed.
093     *     errorsEncountered++;
094     *     break;
095     *   }
096     *
097     *   // Try to process the change in a directory server.
098     *   LDAPResult operationResult;
099     *   try
100     *   {
101     *     operationResult = changeRecord.processChange(connection);
102     *     // If we got here, then the change should have been processed
103     *     // successfully.
104     *     changesProcessed++;
105     *   }
106     *   catch (LDAPException le)
107     *   {
108     *     // If we got here, then the change attempt failed.
109     *     operationResult = le.toLDAPResult();
110     *     errorsEncountered++;
111     *   }
112     * }
113     *
114     * ldifReader.close();
115     * </PRE>
116     */
117    public abstract class LDIFChangeRecord
118           implements LDIFRecord
119    {
120      /**
121       * The serial version UID for this serializable class.
122       */
123      private static final long serialVersionUID = 6917212392170911115L;
124    
125    
126    
127      // The set of controls for the LDIF change record.
128      private final List<Control> controls;
129    
130      // The parsed DN for this LDIF change record.
131      private volatile DN parsedDN;
132    
133      // The DN for this LDIF change record.
134      private final String dn;
135    
136    
137    
138      /**
139       * Creates a new LDIF change record with the provided DN.
140       *
141       * @param  dn        The DN of the LDIF change record to create.  It must not
142       *                   be {@code null}.
143       * @param  controls  The set of controls for the change record to create.  It
144       *                   may be {@code null} or empty if no controls are needed.
145       */
146      protected LDIFChangeRecord(final String dn, final List<Control> controls)
147      {
148        ensureNotNull(dn);
149    
150        this.dn = dn;
151        parsedDN = null;
152    
153        if (controls == null)
154        {
155          this.controls = Collections.emptyList();
156        }
157        else
158        {
159          this.controls = Collections.unmodifiableList(controls);
160        }
161      }
162    
163    
164    
165      /**
166       * Retrieves the DN for this LDIF change record.
167       *
168       * @return  The DN for this LDIF change record.
169       */
170      public final String getDN()
171      {
172        return dn;
173      }
174    
175    
176    
177      /**
178       * Retrieves the parsed DN for this LDIF change record.
179       *
180       * @return  The DN for this LDIF change record.
181       *
182       * @throws  LDAPException  If a problem occurs while trying to parse the DN.
183       */
184      public final DN getParsedDN()
185             throws LDAPException
186      {
187        if (parsedDN == null)
188        {
189          parsedDN = new DN(dn);
190        }
191    
192        return parsedDN;
193      }
194    
195    
196    
197      /**
198       * Retrieves the type of operation represented by this LDIF change record.
199       *
200       * @return  The type of operation represented by this LDIF change record.
201       */
202      public abstract ChangeType getChangeType();
203    
204    
205    
206      /**
207       * Retrieves the set of controls for this LDIF change record.
208       *
209       * @return  The set of controls for this LDIF change record, or an empty array
210       *          if there are no controls.
211       */
212      public List<Control> getControls()
213      {
214        return controls;
215      }
216    
217    
218    
219      /**
220       * Apply the change represented by this LDIF change record to a directory
221       * server using the provided connection.  Any controls included in the
222       * change record will be included in the request.
223       *
224       * @param  connection  The connection to use to apply the change.
225       *
226       * @return  An object providing information about the result of the operation.
227       *
228       * @throws  LDAPException  If an error occurs while processing this change
229       *                         in the associated directory server.
230       */
231      public final LDAPResult processChange(final LDAPInterface connection)
232             throws LDAPException
233      {
234        return processChange(connection, true);
235      }
236    
237    
238    
239      /**
240       * Apply the change represented by this LDIF change record to a directory
241       * server using the provided connection, optionally including any change
242       * record controls in the request.
243       *
244       * @param  connection       The connection to use to apply the change.
245       * @param  includeControls  Indicates whether to include any controls in the
246       *                          request.
247       *
248       * @return  An object providing information about the result of the operation.
249       *
250       * @throws  LDAPException  If an error occurs while processing this change
251       *                         in the associated directory server.
252       */
253      public abstract LDAPResult processChange(final LDAPInterface connection,
254                                               final boolean includeControls)
255             throws LDAPException;
256    
257    
258    
259      /**
260       * Retrieves an {@code Entry} representation of this change record.  This is
261       * intended only for internal use by the LDIF reader when operating
262       * asynchronously in the case that it is not possible to know ahead of time
263       * whether a user will attempt to read an LDIF record by {@code readEntry} or
264       * {@code readChangeRecord}.  In the event that the LDIF file has an entry
265       * whose first attribute is "changetype" and the client wants to read it as
266       * an entry rather than a change record, then this may be used to generate an
267       * entry representing the change record.
268       *
269       * @return  The entry representation of this change record.
270       *
271       * @throws  LDIFException  If this change record cannot be represented as a
272       *                         valid entry.
273       */
274      final Entry toEntry()
275            throws LDIFException
276      {
277        return new Entry(toLDIF());
278      }
279    
280    
281    
282      /**
283       * Retrieves a string array whose lines contain an LDIF representation of this
284       * change record.
285       *
286       * @return  A string array whose lines contain an LDIF representation of this
287       *          change record.
288       */
289      public final String[] toLDIF()
290      {
291        return toLDIF(0);
292      }
293    
294    
295    
296      /**
297       * Retrieves a string array whose lines contain an LDIF representation of this
298       * change record.
299       *
300       * @param  wrapColumn  The column at which to wrap long lines.  A value that
301       *                     is less than or equal to two indicates that no
302       *                     wrapping should be performed.
303       *
304       * @return  A string array whose lines contain an LDIF representation of this
305       *          change record.
306       */
307      public abstract String[] toLDIF(final int wrapColumn);
308    
309    
310    
311      /**
312       * Encodes the provided name and value and adds the result to the provided
313       * list of lines.  This will handle the case in which the encoded name and
314       * value includes comments about the base64-decoded representation of the
315       * provided value.
316       *
317       * @param  name   The attribute name to be encoded.
318       * @param  value  The attribute value to be encoded.
319       * @param  lines  The list of lines to be updated.
320       */
321      static void encodeNameAndValue(final String name, final ASN1OctetString value,
322                                     final List<String> lines)
323      {
324        final String line = LDIFWriter.encodeNameAndValue(name, value);
325        if (LDIFWriter.commentAboutBase64EncodedValues() &&
326            line.startsWith(name + "::"))
327        {
328          final StringTokenizer tokenizer = new StringTokenizer(line, "\r\n");
329          while (tokenizer.hasMoreTokens())
330          {
331            lines.add(tokenizer.nextToken());
332          }
333        }
334        else
335        {
336          lines.add(line);
337        }
338      }
339    
340    
341    
342      /**
343       * Appends an LDIF string representation of this change record to the provided
344       * buffer.
345       *
346       * @param  buffer  The buffer to which to append an LDIF representation of
347       *                 this change record.
348       */
349      public final void toLDIF(final ByteStringBuffer buffer)
350      {
351        toLDIF(buffer, 0);
352      }
353    
354    
355    
356      /**
357       * Appends an LDIF string representation of this change record to the provided
358       * buffer.
359       *
360       * @param  buffer      The buffer to which to append an LDIF representation of
361       *                     this change record.
362       * @param  wrapColumn  The column at which to wrap long lines.  A value that
363       *                     is less than or equal to two indicates that no
364       *                     wrapping should be performed.
365       */
366      public abstract void toLDIF(final ByteStringBuffer buffer,
367                                  final int wrapColumn);
368    
369    
370    
371      /**
372       * Retrieves an LDIF string representation of this change record.
373       *
374       * @return  An LDIF string representation of this change record.
375       */
376      public final String toLDIFString()
377      {
378        final StringBuilder buffer = new StringBuilder();
379        toLDIFString(buffer, 0);
380        return buffer.toString();
381      }
382    
383    
384    
385      /**
386       * Retrieves an LDIF string representation of this change record.
387       *
388       * @param  wrapColumn  The column at which to wrap long lines.  A value that
389       *                     is less than or equal to two indicates that no
390       *                     wrapping should be performed.
391       *
392       * @return  An LDIF string representation of this change record.
393       */
394      public final String toLDIFString(final int wrapColumn)
395      {
396        final StringBuilder buffer = new StringBuilder();
397        toLDIFString(buffer, wrapColumn);
398        return buffer.toString();
399      }
400    
401    
402    
403      /**
404       * Appends an LDIF string representation of this change record to the provided
405       * buffer.
406       *
407       * @param  buffer  The buffer to which to append an LDIF representation of
408       *                 this change record.
409       */
410      public final void toLDIFString(final StringBuilder buffer)
411      {
412        toLDIFString(buffer, 0);
413      }
414    
415    
416    
417      /**
418       * Appends an LDIF string representation of this change record to the provided
419       * buffer.
420       *
421       * @param  buffer      The buffer to which to append an LDIF representation of
422       *                     this change record.
423       * @param  wrapColumn  The column at which to wrap long lines.  A value that
424       *                     is less than or equal to two indicates that no
425       *                     wrapping should be performed.
426       */
427      public abstract void toLDIFString(final StringBuilder buffer,
428                                        final int wrapColumn);
429    
430    
431    
432      /**
433       * Retrieves a hash code for this change record.
434       *
435       * @return  A hash code for this change record.
436       */
437      @Override()
438      public abstract int hashCode();
439    
440    
441    
442      /**
443       * Indicates whether the provided object is equal to this LDIF change record.
444       *
445       * @param  o  The object for which to make the determination.
446       *
447       * @return  {@code true} if the provided object is equal to this LDIF change
448       *          record, or {@code false} if not.
449       */
450      @Override()
451      public abstract boolean equals(final Object o);
452    
453    
454    
455      /**
456       * Encodes a string representation of the provided control for use in the
457       * LDIF representation of the change record.
458       *
459       * @param  c  The control to be encoded.
460       *
461       * @return  The string representation of the control.
462       */
463      static ASN1OctetString encodeControlString(final Control c)
464      {
465        final ByteStringBuffer buffer = new ByteStringBuffer();
466        buffer.append(c.getOID());
467    
468        if (c.isCritical())
469        {
470          buffer.append(" true");
471        }
472        else
473        {
474          buffer.append(" false");
475        }
476    
477        final ASN1OctetString value = c.getValue();
478        if (value != null)
479        {
480          LDIFWriter.encodeValue(value, buffer);
481        }
482    
483        return buffer.toByteString().toASN1OctetString();
484      }
485    
486    
487    
488      /**
489       * Retrieves a single-line string representation of this change record.
490       *
491       * @return  A single-line string representation of this change record.
492       */
493      @Override()
494      public final String toString()
495      {
496        final StringBuilder buffer = new StringBuilder();
497        toString(buffer);
498        return buffer.toString();
499      }
500    
501    
502    
503      /**
504       * Appends a single-line string representation of this change record to the
505       * provided buffer.
506       *
507       * @param  buffer  The buffer to which the information should be written.
508       */
509      public abstract void toString(final StringBuilder buffer);
510    }