001/*
002 * Copyright 2007-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2018 Ping Identity Corporation
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;
041import 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)
121public 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    Validator.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  @Override()
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  @Override()
190  public final DN getParsedDN()
191         throws LDAPException
192  {
193    if (parsedDN == null)
194    {
195      parsedDN = new DN(dn);
196    }
197
198    return parsedDN;
199  }
200
201
202
203  /**
204   * Retrieves the type of operation represented by this LDIF change record.
205   *
206   * @return  The type of operation represented by this LDIF change record.
207   */
208  public abstract ChangeType getChangeType();
209
210
211
212  /**
213   * Retrieves the set of controls for this LDIF change record.
214   *
215   * @return  The set of controls for this LDIF change record, or an empty array
216   *          if there are no controls.
217   */
218  public List<Control> getControls()
219  {
220    return controls;
221  }
222
223
224
225  /**
226   * Apply the change represented by this LDIF change record to a directory
227   * server using the provided connection.  Any controls included in the
228   * change record will be included in the request.
229   *
230   * @param  connection  The connection to use to apply the change.
231   *
232   * @return  An object providing information about the result of the operation.
233   *
234   * @throws  LDAPException  If an error occurs while processing this change
235   *                         in the associated directory server.
236   */
237  public final LDAPResult processChange(final LDAPInterface connection)
238         throws LDAPException
239  {
240    return processChange(connection, true);
241  }
242
243
244
245  /**
246   * Apply the change represented by this LDIF change record to a directory
247   * server using the provided connection, optionally including any change
248   * record controls in the request.
249   *
250   * @param  connection       The connection to use to apply the change.
251   * @param  includeControls  Indicates whether to include any controls in the
252   *                          request.
253   *
254   * @return  An object providing information about the result of the operation.
255   *
256   * @throws  LDAPException  If an error occurs while processing this change
257   *                         in the associated directory server.
258   */
259  public abstract LDAPResult processChange(LDAPInterface connection,
260                                           boolean includeControls)
261         throws LDAPException;
262
263
264
265  /**
266   * Retrieves an {@code Entry} representation of this change record.  This is
267   * intended only for internal use by the LDIF reader when operating
268   * asynchronously in the case that it is not possible to know ahead of time
269   * whether a user will attempt to read an LDIF record by {@code readEntry} or
270   * {@code readChangeRecord}.  In the event that the LDIF file has an entry
271   * whose first attribute is "changetype" and the client wants to read it as
272   * an entry rather than a change record, then this may be used to generate an
273   * entry representing the change record.
274   *
275   * @return  The entry representation of this change record.
276   *
277   * @throws  LDIFException  If this change record cannot be represented as a
278   *                         valid entry.
279   */
280  final Entry toEntry()
281        throws LDIFException
282  {
283    return new Entry(toLDIF());
284  }
285
286
287
288  /**
289   * Retrieves a string array whose lines contain an LDIF representation of this
290   * change record.
291   *
292   * @return  A string array whose lines contain an LDIF representation of this
293   *          change record.
294   */
295  @Override()
296  public final String[] toLDIF()
297  {
298    return toLDIF(0);
299  }
300
301
302
303  /**
304   * Retrieves a string array whose lines contain an LDIF representation of this
305   * change record.
306   *
307   * @param  wrapColumn  The column at which to wrap long lines.  A value that
308   *                     is less than or equal to two indicates that no
309   *                     wrapping should be performed.
310   *
311   * @return  A string array whose lines contain an LDIF representation of this
312   *          change record.
313   */
314  @Override()
315  public abstract String[] toLDIF(int wrapColumn);
316
317
318
319  /**
320   * Encodes the provided name and value and adds the result to the provided
321   * list of lines.  This will handle the case in which the encoded name and
322   * value includes comments about the base64-decoded representation of the
323   * provided value.
324   *
325   * @param  name   The attribute name to be encoded.
326   * @param  value  The attribute value to be encoded.
327   * @param  lines  The list of lines to be updated.
328   */
329  static void encodeNameAndValue(final String name, final ASN1OctetString value,
330                                 final List<String> lines)
331  {
332    final String line = LDIFWriter.encodeNameAndValue(name, value);
333    if (LDIFWriter.commentAboutBase64EncodedValues() &&
334        line.startsWith(name + "::"))
335    {
336      final StringTokenizer tokenizer = new StringTokenizer(line, "\r\n");
337      while (tokenizer.hasMoreTokens())
338      {
339        lines.add(tokenizer.nextToken());
340      }
341    }
342    else
343    {
344      lines.add(line);
345    }
346  }
347
348
349
350  /**
351   * Appends an LDIF string representation of this change record to the provided
352   * buffer.
353   *
354   * @param  buffer  The buffer to which to append an LDIF representation of
355   *                 this change record.
356   */
357  @Override()
358  public final void toLDIF(final ByteStringBuffer buffer)
359  {
360    toLDIF(buffer, 0);
361  }
362
363
364
365  /**
366   * Appends an LDIF string representation of this change record to the provided
367   * buffer.
368   *
369   * @param  buffer      The buffer to which to append an LDIF representation of
370   *                     this change record.
371   * @param  wrapColumn  The column at which to wrap long lines.  A value that
372   *                     is less than or equal to two indicates that no
373   *                     wrapping should be performed.
374   */
375  @Override()
376  public abstract void toLDIF(ByteStringBuffer buffer, int wrapColumn);
377
378
379
380  /**
381   * Retrieves an LDIF string representation of this change record.
382   *
383   * @return  An LDIF string representation of this change record.
384   */
385  @Override()
386  public final String toLDIFString()
387  {
388    final StringBuilder buffer = new StringBuilder();
389    toLDIFString(buffer, 0);
390    return buffer.toString();
391  }
392
393
394
395  /**
396   * Retrieves an LDIF string representation of this change record.
397   *
398   * @param  wrapColumn  The column at which to wrap long lines.  A value that
399   *                     is less than or equal to two indicates that no
400   *                     wrapping should be performed.
401   *
402   * @return  An LDIF string representation of this change record.
403   */
404  @Override()
405  public final String toLDIFString(final int wrapColumn)
406  {
407    final StringBuilder buffer = new StringBuilder();
408    toLDIFString(buffer, wrapColumn);
409    return buffer.toString();
410  }
411
412
413
414  /**
415   * Appends an LDIF string representation of this change record to the provided
416   * buffer.
417   *
418   * @param  buffer  The buffer to which to append an LDIF representation of
419   *                 this change record.
420   */
421  @Override()
422  public final void toLDIFString(final StringBuilder buffer)
423  {
424    toLDIFString(buffer, 0);
425  }
426
427
428
429  /**
430   * Appends an LDIF string representation of this change record to the provided
431   * buffer.
432   *
433   * @param  buffer      The buffer to which to append an LDIF representation of
434   *                     this change record.
435   * @param  wrapColumn  The column at which to wrap long lines.  A value that
436   *                     is less than or equal to two indicates that no
437   *                     wrapping should be performed.
438   */
439  @Override()
440  public abstract void toLDIFString(StringBuilder buffer, int wrapColumn);
441
442
443
444  /**
445   * Retrieves a hash code for this change record.
446   *
447   * @return  A hash code for this change record.
448   */
449  @Override()
450  public abstract int hashCode();
451
452
453
454  /**
455   * Indicates whether the provided object is equal to this LDIF change record.
456   *
457   * @param  o  The object for which to make the determination.
458   *
459   * @return  {@code true} if the provided object is equal to this LDIF change
460   *          record, or {@code false} if not.
461   */
462  @Override()
463  public abstract boolean equals(Object o);
464
465
466
467  /**
468   * Encodes a string representation of the provided control for use in the
469   * LDIF representation of the change record.
470   *
471   * @param  c  The control to be encoded.
472   *
473   * @return  The string representation of the control.
474   */
475  static ASN1OctetString encodeControlString(final Control c)
476  {
477    final ByteStringBuffer buffer = new ByteStringBuffer();
478    buffer.append(c.getOID());
479
480    if (c.isCritical())
481    {
482      buffer.append(" true");
483    }
484    else
485    {
486      buffer.append(" false");
487    }
488
489    final ASN1OctetString value = c.getValue();
490    if (value != null)
491    {
492      LDIFWriter.encodeValue(value, buffer);
493    }
494
495    return buffer.toByteString().toASN1OctetString();
496  }
497
498
499
500  /**
501   * Retrieves a single-line string representation of this change record.
502   *
503   * @return  A single-line string representation of this change record.
504   */
505  @Override()
506  public final String toString()
507  {
508    final StringBuilder buffer = new StringBuilder();
509    toString(buffer);
510    return buffer.toString();
511  }
512
513
514
515  /**
516   * Appends a single-line string representation of this change record to the
517   * provided buffer.
518   *
519   * @param  buffer  The buffer to which the information should be written.
520   */
521  @Override()
522  public abstract void toString(StringBuilder buffer);
523}