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