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