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