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 }