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