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.ArrayList; 041import java.util.HashSet; 042import java.util.Iterator; 043import java.util.List; 044 045import com.unboundid.asn1.ASN1OctetString; 046import com.unboundid.ldap.sdk.ChangeType; 047import com.unboundid.ldap.sdk.Control; 048import com.unboundid.ldap.sdk.LDAPException; 049import com.unboundid.ldap.sdk.LDAPInterface; 050import com.unboundid.ldap.sdk.LDAPResult; 051import com.unboundid.ldap.sdk.Modification; 052import com.unboundid.ldap.sdk.ModifyRequest; 053import com.unboundid.util.ByteStringBuffer; 054import com.unboundid.util.Debug; 055import com.unboundid.util.NotMutable; 056import com.unboundid.util.NotNull; 057import com.unboundid.util.Nullable; 058import com.unboundid.util.PropertyManager; 059import com.unboundid.util.StaticUtils; 060import com.unboundid.util.ThreadSafety; 061import com.unboundid.util.ThreadSafetyLevel; 062import com.unboundid.util.Validator; 063 064 065 066/** 067 * This class defines an LDIF modify change record, which can be used to 068 * represent an LDAP modify request. See the documentation for the 069 * {@link LDIFChangeRecord} class for an example demonstrating the process for 070 * interacting with LDIF change records. 071 */ 072@NotMutable() 073@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 074public final class LDIFModifyChangeRecord 075 extends LDIFChangeRecord 076{ 077 /** 078 * The name of the system property that will be used to indicate whether 079 * to always include a trailing dash after the last change in the LDIF 080 * representation of a modify change record. By default, the dash will always 081 * be included. 082 */ 083 @NotNull public static final String PROPERTY_ALWAYS_INCLUDE_TRAILING_DASH = 084 "com.unboundid.ldif.modify.alwaysIncludeTrailingDash"; 085 086 087 088 /** 089 * Indicates whether to always include a trailing dash after the last change 090 * in the LDIF representation. 091 */ 092 private static boolean alwaysIncludeTrailingDash = PropertyManager.getBoolean( 093 PROPERTY_ALWAYS_INCLUDE_TRAILING_DASH, true); 094 095 096 097 /** 098 * The serial version UID for this serializable class. 099 */ 100 private static final long serialVersionUID = -7558098319600288036L; 101 102 103 104 // The set of modifications for this modify change record. 105 @NotNull private final Modification[] modifications; 106 107 108 109 /** 110 * Creates a new LDIF modify change record with the provided DN and set of 111 * modifications. 112 * 113 * @param dn The DN for this LDIF add change record. It must not 114 * be {@code null}. 115 * @param modifications The set of modifications for this LDIF modify change 116 * record. It must not be {@code null} or empty. 117 */ 118 public LDIFModifyChangeRecord(@NotNull final String dn, 119 @NotNull final Modification... modifications) 120 { 121 this(dn, modifications, null); 122 } 123 124 125 126 /** 127 * Creates a new LDIF modify change record with the provided DN and set of 128 * modifications. 129 * 130 * @param dn The DN for this LDIF add change record. It must not 131 * be {@code null}. 132 * @param modifications The set of modifications for this LDIF modify change 133 * record. It must not be {@code null} or empty. 134 * @param controls The set of controls for this LDIF modify change 135 * record. It may be {@code null} or empty if there 136 * are no controls. 137 */ 138 public LDIFModifyChangeRecord(@NotNull final String dn, 139 @NotNull final Modification[] modifications, 140 @Nullable final List<Control> controls) 141 { 142 super(dn, controls); 143 144 Validator.ensureNotNull(modifications); 145 Validator.ensureTrue(modifications.length > 0, 146 "LDIFModifyChangeRecord.modifications must not be empty."); 147 148 this.modifications = modifications; 149 } 150 151 152 153 /** 154 * Creates a new LDIF modify change record with the provided DN and set of 155 * modifications. 156 * 157 * @param dn The DN for this LDIF add change record. It must not 158 * be {@code null}. 159 * @param modifications The set of modifications for this LDIF modify change 160 * record. It must not be {@code null} or empty. 161 */ 162 public LDIFModifyChangeRecord(@NotNull final String dn, 163 @NotNull final List<Modification> modifications) 164 { 165 this(dn, modifications, null); 166 } 167 168 169 170 /** 171 * Creates a new LDIF modify change record with the provided DN and set of 172 * modifications. 173 * 174 * @param dn The DN for this LDIF add change record. It must not 175 * be {@code null}. 176 * @param modifications The set of modifications for this LDIF modify change 177 * record. It must not be {@code null} or empty. 178 * @param controls The set of controls for this LDIF modify change 179 * record. It may be {@code null} or empty if there 180 * are no controls. 181 */ 182 public LDIFModifyChangeRecord(@NotNull final String dn, 183 @NotNull final List<Modification> modifications, 184 @Nullable final List<Control> controls) 185 { 186 super(dn, controls); 187 188 Validator.ensureNotNull(modifications); 189 Validator.ensureFalse(modifications.isEmpty(), 190 "LDIFModifyChangeRecord.modifications must not be empty."); 191 192 this.modifications = new Modification[modifications.size()]; 193 modifications.toArray(this.modifications); 194 } 195 196 197 198 /** 199 * Creates a new LDIF modify change record from the provided modify request. 200 * 201 * @param modifyRequest The modify request to use to create this LDIF modify 202 * change record. It must not be {@code null}. 203 */ 204 public LDIFModifyChangeRecord(@NotNull final ModifyRequest modifyRequest) 205 { 206 super(modifyRequest.getDN(), modifyRequest.getControlList()); 207 208 final List<Modification> mods = modifyRequest.getModifications(); 209 modifications = new Modification[mods.size()]; 210 211 final Iterator<Modification> iterator = mods.iterator(); 212 for (int i=0; i < modifications.length; i++) 213 { 214 modifications[i] = iterator.next(); 215 } 216 } 217 218 219 220 /** 221 * Indicates whether the LDIF representation of a modify change record should 222 * always include a trailing dash after the last (or only) change. 223 * 224 * @return {@code true} if the LDIF representation of a modify change record 225 * should always include a trailing dash after the last (or only) 226 * change, or {@code false} if not. 227 */ 228 public static boolean alwaysIncludeTrailingDash() 229 { 230 return alwaysIncludeTrailingDash; 231 } 232 233 234 235 /** 236 * Specifies whether the LDIF representation of a modify change record should 237 * always include a trailing dash after the last (or only) change. 238 * 239 * @param alwaysIncludeTrailingDash Indicates whether the LDIF 240 * representation of a modify change record 241 * should always include a trailing dash 242 * after the last (or only) change. 243 */ 244 public static void setAlwaysIncludeTrailingDash( 245 final boolean alwaysIncludeTrailingDash) 246 { 247 LDIFModifyChangeRecord.alwaysIncludeTrailingDash = 248 alwaysIncludeTrailingDash; 249 } 250 251 252 253 /** 254 * Retrieves the set of modifications for this modify change record. 255 * 256 * @return The set of modifications for this modify change record. 257 */ 258 @NotNull() 259 public Modification[] getModifications() 260 { 261 return modifications; 262 } 263 264 265 266 /** 267 * Creates a modify request from this LDIF modify change record. Any change 268 * record controls will be included in the request 269 * 270 * @return The modify request created from this LDIF modify change record. 271 */ 272 @NotNull() 273 public ModifyRequest toModifyRequest() 274 { 275 return toModifyRequest(true); 276 } 277 278 279 280 /** 281 * Creates a modify request from this LDIF modify change record, optionally 282 * including any change record controls in the request. 283 * 284 * @param includeControls Indicates whether to include any controls in the 285 * request. 286 * 287 * @return The modify request created from this LDIF modify change record. 288 */ 289 @NotNull() 290 public ModifyRequest toModifyRequest(final boolean includeControls) 291 { 292 final ModifyRequest modifyRequest = 293 new ModifyRequest(getDN(), modifications); 294 if (includeControls) 295 { 296 modifyRequest.setControls(getControls()); 297 } 298 299 return modifyRequest; 300 } 301 302 303 304 /** 305 * {@inheritDoc} 306 */ 307 @Override() 308 @NotNull() 309 public ChangeType getChangeType() 310 { 311 return ChangeType.MODIFY; 312 } 313 314 315 316 /** 317 * {@inheritDoc} 318 */ 319 @Override() 320 @NotNull() 321 public LDIFModifyChangeRecord duplicate(@Nullable final Control... controls) 322 { 323 return new LDIFModifyChangeRecord(getDN(), modifications, 324 StaticUtils.toList(controls)); 325 } 326 327 328 329 /** 330 * {@inheritDoc} 331 */ 332 @Override() 333 @NotNull() 334 public LDAPResult processChange(@NotNull final LDAPInterface connection, 335 final boolean includeControls) 336 throws LDAPException 337 { 338 return connection.modify(toModifyRequest(includeControls)); 339 } 340 341 342 343 /** 344 * {@inheritDoc} 345 */ 346 @Override() 347 @NotNull() 348 public String[] toLDIF(final int wrapColumn) 349 { 350 List<String> ldifLines = new ArrayList<>(modifications.length*4); 351 encodeNameAndValue("dn", new ASN1OctetString(getDN()), ldifLines); 352 353 for (final Control c : getControls()) 354 { 355 encodeNameAndValue("control", encodeControlString(c), ldifLines); 356 } 357 358 ldifLines.add("changetype: modify"); 359 360 for (int i=0; i < modifications.length; i++) 361 { 362 final String attrName = modifications[i].getAttributeName(); 363 364 switch (modifications[i].getModificationType().intValue()) 365 { 366 case 0: 367 ldifLines.add("add: " + attrName); 368 break; 369 case 1: 370 ldifLines.add("delete: " + attrName); 371 break; 372 case 2: 373 ldifLines.add("replace: " + attrName); 374 break; 375 case 3: 376 ldifLines.add("increment: " + attrName); 377 break; 378 default: 379 // This should never happen. 380 continue; 381 } 382 383 for (final ASN1OctetString value : modifications[i].getRawValues()) 384 { 385 encodeNameAndValue(attrName, value, ldifLines); 386 } 387 388 if (alwaysIncludeTrailingDash || (i < (modifications.length - 1))) 389 { 390 ldifLines.add("-"); 391 } 392 } 393 394 if (wrapColumn > 2) 395 { 396 ldifLines = LDIFWriter.wrapLines(wrapColumn, ldifLines); 397 } 398 399 final String[] ldifArray = new String[ldifLines.size()]; 400 ldifLines.toArray(ldifArray); 401 return ldifArray; 402 } 403 404 405 406 /** 407 * {@inheritDoc} 408 */ 409 @Override() 410 public void toLDIF(@NotNull final ByteStringBuffer buffer, 411 final int wrapColumn) 412 { 413 LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(getDN()), buffer, 414 wrapColumn); 415 buffer.append(StaticUtils.EOL_BYTES); 416 417 for (final Control c : getControls()) 418 { 419 LDIFWriter.encodeNameAndValue("control", encodeControlString(c), buffer, 420 wrapColumn); 421 buffer.append(StaticUtils.EOL_BYTES); 422 } 423 424 LDIFWriter.encodeNameAndValue("changetype", new ASN1OctetString("modify"), 425 buffer, wrapColumn); 426 buffer.append(StaticUtils.EOL_BYTES); 427 428 for (int i=0; i < modifications.length; i++) 429 { 430 final String attrName = modifications[i].getAttributeName(); 431 432 switch (modifications[i].getModificationType().intValue()) 433 { 434 case 0: 435 LDIFWriter.encodeNameAndValue("add", new ASN1OctetString(attrName), 436 buffer, wrapColumn); 437 buffer.append(StaticUtils.EOL_BYTES); 438 break; 439 case 1: 440 LDIFWriter.encodeNameAndValue("delete", new ASN1OctetString(attrName), 441 buffer, wrapColumn); 442 buffer.append(StaticUtils.EOL_BYTES); 443 break; 444 case 2: 445 LDIFWriter.encodeNameAndValue("replace", 446 new ASN1OctetString(attrName), buffer, 447 wrapColumn); 448 buffer.append(StaticUtils.EOL_BYTES); 449 break; 450 case 3: 451 LDIFWriter.encodeNameAndValue("increment", 452 new ASN1OctetString(attrName), buffer, 453 wrapColumn); 454 buffer.append(StaticUtils.EOL_BYTES); 455 break; 456 default: 457 // This should never happen. 458 continue; 459 } 460 461 for (final ASN1OctetString value : modifications[i].getRawValues()) 462 { 463 LDIFWriter.encodeNameAndValue(attrName, value, buffer, wrapColumn); 464 buffer.append(StaticUtils.EOL_BYTES); 465 } 466 467 if (alwaysIncludeTrailingDash || (i < (modifications.length - 1))) 468 { 469 buffer.append('-'); 470 buffer.append(StaticUtils.EOL_BYTES); 471 } 472 } 473 } 474 475 476 477 /** 478 * {@inheritDoc} 479 */ 480 @Override() 481 public void toLDIFString(@NotNull final StringBuilder buffer, 482 final int wrapColumn) 483 { 484 LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(getDN()), buffer, 485 wrapColumn); 486 buffer.append(StaticUtils.EOL); 487 488 for (final Control c : getControls()) 489 { 490 LDIFWriter.encodeNameAndValue("control", encodeControlString(c), buffer, 491 wrapColumn); 492 buffer.append(StaticUtils.EOL); 493 } 494 495 LDIFWriter.encodeNameAndValue("changetype", new ASN1OctetString("modify"), 496 buffer, wrapColumn); 497 buffer.append(StaticUtils.EOL); 498 499 for (int i=0; i < modifications.length; i++) 500 { 501 final String attrName = modifications[i].getAttributeName(); 502 503 switch (modifications[i].getModificationType().intValue()) 504 { 505 case 0: 506 LDIFWriter.encodeNameAndValue("add", new ASN1OctetString(attrName), 507 buffer, wrapColumn); 508 buffer.append(StaticUtils.EOL); 509 break; 510 case 1: 511 LDIFWriter.encodeNameAndValue("delete", new ASN1OctetString(attrName), 512 buffer, wrapColumn); 513 buffer.append(StaticUtils.EOL); 514 break; 515 case 2: 516 LDIFWriter.encodeNameAndValue("replace", 517 new ASN1OctetString(attrName), buffer, 518 wrapColumn); 519 buffer.append(StaticUtils.EOL); 520 break; 521 case 3: 522 LDIFWriter.encodeNameAndValue("increment", 523 new ASN1OctetString(attrName), buffer, 524 wrapColumn); 525 buffer.append(StaticUtils.EOL); 526 break; 527 default: 528 // This should never happen. 529 continue; 530 } 531 532 for (final ASN1OctetString value : modifications[i].getRawValues()) 533 { 534 LDIFWriter.encodeNameAndValue(attrName, value, buffer, wrapColumn); 535 buffer.append(StaticUtils.EOL); 536 } 537 538 if (alwaysIncludeTrailingDash || (i < (modifications.length - 1))) 539 { 540 buffer.append('-'); 541 buffer.append(StaticUtils.EOL); 542 } 543 } 544 } 545 546 547 548 /** 549 * {@inheritDoc} 550 */ 551 @Override() 552 public int hashCode() 553 { 554 int hashCode; 555 try 556 { 557 hashCode = getParsedDN().hashCode(); 558 } 559 catch (final Exception e) 560 { 561 Debug.debugException(e); 562 hashCode = StaticUtils.toLowerCase(getDN()).hashCode(); 563 } 564 565 for (final Modification m : modifications) 566 { 567 hashCode += m.hashCode(); 568 } 569 570 return hashCode; 571 } 572 573 574 575 /** 576 * {@inheritDoc} 577 */ 578 @Override() 579 public boolean equals(@Nullable final Object o) 580 { 581 if (o == null) 582 { 583 return false; 584 } 585 586 if (o == this) 587 { 588 return true; 589 } 590 591 if (! (o instanceof LDIFModifyChangeRecord)) 592 { 593 return false; 594 } 595 596 final LDIFModifyChangeRecord r = (LDIFModifyChangeRecord) o; 597 598 final HashSet<Control> c1 = new HashSet<>(getControls()); 599 final HashSet<Control> c2 = new HashSet<>(r.getControls()); 600 if (! c1.equals(c2)) 601 { 602 return false; 603 } 604 605 try 606 { 607 if (! getParsedDN().equals(r.getParsedDN())) 608 { 609 return false; 610 } 611 } 612 catch (final Exception e) 613 { 614 Debug.debugException(e); 615 if (! StaticUtils.toLowerCase(getDN()).equals( 616 StaticUtils.toLowerCase(r.getDN()))) 617 { 618 return false; 619 } 620 } 621 622 if (modifications.length != r.modifications.length) 623 { 624 return false; 625 } 626 627 for (int i=0; i < modifications.length; i++) 628 { 629 if (! modifications[i].equals(r.modifications[i])) 630 { 631 return false; 632 } 633 } 634 635 return true; 636 } 637 638 639 640 /** 641 * {@inheritDoc} 642 */ 643 @Override() 644 public void toString(@NotNull final StringBuilder buffer) 645 { 646 buffer.append("LDIFModifyChangeRecord(dn='"); 647 buffer.append(getDN()); 648 buffer.append("', mods={"); 649 650 for (int i=0; i < modifications.length; i++) 651 { 652 if (i > 0) 653 { 654 buffer.append(", "); 655 } 656 modifications[i].toString(buffer); 657 } 658 buffer.append('}'); 659 660 final List<Control> controls = getControls(); 661 if (! controls.isEmpty()) 662 { 663 buffer.append(", controls={"); 664 665 final Iterator<Control> iterator = controls.iterator(); 666 while (iterator.hasNext()) 667 { 668 iterator.next().toString(buffer); 669 if (iterator.hasNext()) 670 { 671 buffer.append(','); 672 } 673 } 674 675 buffer.append('}'); 676 } 677 678 buffer.append(')'); 679 } 680}