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}