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}