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}