001/*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 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 */
021package com.unboundid.ldif;
022
023
024
025import java.util.Collections;
026import java.util.List;
027import java.util.StringTokenizer;
028
029import com.unboundid.asn1.ASN1OctetString;
030import com.unboundid.ldap.sdk.ChangeType;
031import com.unboundid.ldap.sdk.Control;
032import com.unboundid.ldap.sdk.DN;
033import com.unboundid.ldap.sdk.Entry;
034import com.unboundid.ldap.sdk.LDAPException;
035import com.unboundid.ldap.sdk.LDAPInterface;
036import com.unboundid.ldap.sdk.LDAPResult;
037import com.unboundid.util.ByteStringBuffer;
038import com.unboundid.util.NotExtensible;
039import com.unboundid.util.ThreadSafety;
040import com.unboundid.util.ThreadSafetyLevel;
041
042import 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)
122public 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}