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