001/* 002 * Copyright 2007-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2018 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 LDAPResult processChange(final LDAPInterface connection, 310 final boolean includeControls) 311 throws LDAPException 312 { 313 return connection.modify(toModifyRequest(includeControls)); 314 } 315 316 317 318 /** 319 * {@inheritDoc} 320 */ 321 @Override() 322 public String[] toLDIF(final int wrapColumn) 323 { 324 List<String> ldifLines = new ArrayList<>(modifications.length*4); 325 encodeNameAndValue("dn", new ASN1OctetString(getDN()), ldifLines); 326 327 for (final Control c : getControls()) 328 { 329 encodeNameAndValue("control", encodeControlString(c), ldifLines); 330 } 331 332 ldifLines.add("changetype: modify"); 333 334 for (int i=0; i < modifications.length; i++) 335 { 336 final String attrName = modifications[i].getAttributeName(); 337 338 switch (modifications[i].getModificationType().intValue()) 339 { 340 case 0: 341 ldifLines.add("add: " + attrName); 342 break; 343 case 1: 344 ldifLines.add("delete: " + attrName); 345 break; 346 case 2: 347 ldifLines.add("replace: " + attrName); 348 break; 349 case 3: 350 ldifLines.add("increment: " + attrName); 351 break; 352 default: 353 // This should never happen. 354 continue; 355 } 356 357 for (final ASN1OctetString value : modifications[i].getRawValues()) 358 { 359 encodeNameAndValue(attrName, value, ldifLines); 360 } 361 362 if (alwaysIncludeTrailingDash || (i < (modifications.length - 1))) 363 { 364 ldifLines.add("-"); 365 } 366 } 367 368 if (wrapColumn > 2) 369 { 370 ldifLines = LDIFWriter.wrapLines(wrapColumn, ldifLines); 371 } 372 373 final String[] ldifArray = new String[ldifLines.size()]; 374 ldifLines.toArray(ldifArray); 375 return ldifArray; 376 } 377 378 379 380 /** 381 * {@inheritDoc} 382 */ 383 @Override() 384 public void toLDIF(final ByteStringBuffer buffer, final int wrapColumn) 385 { 386 LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(getDN()), buffer, 387 wrapColumn); 388 buffer.append(StaticUtils.EOL_BYTES); 389 390 for (final Control c : getControls()) 391 { 392 LDIFWriter.encodeNameAndValue("control", encodeControlString(c), buffer, 393 wrapColumn); 394 buffer.append(StaticUtils.EOL_BYTES); 395 } 396 397 LDIFWriter.encodeNameAndValue("changetype", new ASN1OctetString("modify"), 398 buffer, wrapColumn); 399 buffer.append(StaticUtils.EOL_BYTES); 400 401 for (int i=0; i < modifications.length; i++) 402 { 403 final String attrName = modifications[i].getAttributeName(); 404 405 switch (modifications[i].getModificationType().intValue()) 406 { 407 case 0: 408 LDIFWriter.encodeNameAndValue("add", new ASN1OctetString(attrName), 409 buffer, wrapColumn); 410 buffer.append(StaticUtils.EOL_BYTES); 411 break; 412 case 1: 413 LDIFWriter.encodeNameAndValue("delete", new ASN1OctetString(attrName), 414 buffer, wrapColumn); 415 buffer.append(StaticUtils.EOL_BYTES); 416 break; 417 case 2: 418 LDIFWriter.encodeNameAndValue("replace", 419 new ASN1OctetString(attrName), buffer, 420 wrapColumn); 421 buffer.append(StaticUtils.EOL_BYTES); 422 break; 423 case 3: 424 LDIFWriter.encodeNameAndValue("increment", 425 new ASN1OctetString(attrName), buffer, 426 wrapColumn); 427 buffer.append(StaticUtils.EOL_BYTES); 428 break; 429 default: 430 // This should never happen. 431 continue; 432 } 433 434 for (final ASN1OctetString value : modifications[i].getRawValues()) 435 { 436 LDIFWriter.encodeNameAndValue(attrName, value, buffer, wrapColumn); 437 buffer.append(StaticUtils.EOL_BYTES); 438 } 439 440 if (alwaysIncludeTrailingDash || (i < (modifications.length - 1))) 441 { 442 buffer.append('-'); 443 buffer.append(StaticUtils.EOL_BYTES); 444 } 445 } 446 } 447 448 449 450 /** 451 * {@inheritDoc} 452 */ 453 @Override() 454 public void toLDIFString(final StringBuilder buffer, final int wrapColumn) 455 { 456 LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(getDN()), buffer, 457 wrapColumn); 458 buffer.append(StaticUtils.EOL); 459 460 for (final Control c : getControls()) 461 { 462 LDIFWriter.encodeNameAndValue("control", encodeControlString(c), buffer, 463 wrapColumn); 464 buffer.append(StaticUtils.EOL); 465 } 466 467 LDIFWriter.encodeNameAndValue("changetype", new ASN1OctetString("modify"), 468 buffer, wrapColumn); 469 buffer.append(StaticUtils.EOL); 470 471 for (int i=0; i < modifications.length; i++) 472 { 473 final String attrName = modifications[i].getAttributeName(); 474 475 switch (modifications[i].getModificationType().intValue()) 476 { 477 case 0: 478 LDIFWriter.encodeNameAndValue("add", new ASN1OctetString(attrName), 479 buffer, wrapColumn); 480 buffer.append(StaticUtils.EOL); 481 break; 482 case 1: 483 LDIFWriter.encodeNameAndValue("delete", new ASN1OctetString(attrName), 484 buffer, wrapColumn); 485 buffer.append(StaticUtils.EOL); 486 break; 487 case 2: 488 LDIFWriter.encodeNameAndValue("replace", 489 new ASN1OctetString(attrName), buffer, 490 wrapColumn); 491 buffer.append(StaticUtils.EOL); 492 break; 493 case 3: 494 LDIFWriter.encodeNameAndValue("increment", 495 new ASN1OctetString(attrName), buffer, 496 wrapColumn); 497 buffer.append(StaticUtils.EOL); 498 break; 499 default: 500 // This should never happen. 501 continue; 502 } 503 504 for (final ASN1OctetString value : modifications[i].getRawValues()) 505 { 506 LDIFWriter.encodeNameAndValue(attrName, value, buffer, wrapColumn); 507 buffer.append(StaticUtils.EOL); 508 } 509 510 if (alwaysIncludeTrailingDash || (i < (modifications.length - 1))) 511 { 512 buffer.append('-'); 513 buffer.append(StaticUtils.EOL); 514 } 515 } 516 } 517 518 519 520 /** 521 * {@inheritDoc} 522 */ 523 @Override() 524 public int hashCode() 525 { 526 int hashCode; 527 try 528 { 529 hashCode = getParsedDN().hashCode(); 530 } 531 catch (final Exception e) 532 { 533 Debug.debugException(e); 534 hashCode = StaticUtils.toLowerCase(getDN()).hashCode(); 535 } 536 537 for (final Modification m : modifications) 538 { 539 hashCode += m.hashCode(); 540 } 541 542 return hashCode; 543 } 544 545 546 547 /** 548 * {@inheritDoc} 549 */ 550 @Override() 551 public boolean equals(final Object o) 552 { 553 if (o == null) 554 { 555 return false; 556 } 557 558 if (o == this) 559 { 560 return true; 561 } 562 563 if (! (o instanceof LDIFModifyChangeRecord)) 564 { 565 return false; 566 } 567 568 final LDIFModifyChangeRecord r = (LDIFModifyChangeRecord) o; 569 570 final HashSet<Control> c1 = new HashSet<>(getControls()); 571 final HashSet<Control> c2 = new HashSet<>(r.getControls()); 572 if (! c1.equals(c2)) 573 { 574 return false; 575 } 576 577 try 578 { 579 if (! getParsedDN().equals(r.getParsedDN())) 580 { 581 return false; 582 } 583 } 584 catch (final Exception e) 585 { 586 Debug.debugException(e); 587 if (! StaticUtils.toLowerCase(getDN()).equals( 588 StaticUtils.toLowerCase(r.getDN()))) 589 { 590 return false; 591 } 592 } 593 594 if (modifications.length != r.modifications.length) 595 { 596 return false; 597 } 598 599 for (int i=0; i < modifications.length; i++) 600 { 601 if (! modifications[i].equals(r.modifications[i])) 602 { 603 return false; 604 } 605 } 606 607 return true; 608 } 609 610 611 612 /** 613 * {@inheritDoc} 614 */ 615 @Override() 616 public void toString(final StringBuilder buffer) 617 { 618 buffer.append("LDIFModifyChangeRecord(dn='"); 619 buffer.append(getDN()); 620 buffer.append("', mods={"); 621 622 for (int i=0; i < modifications.length; i++) 623 { 624 if (i > 0) 625 { 626 buffer.append(", "); 627 } 628 modifications[i].toString(buffer); 629 } 630 buffer.append('}'); 631 632 final List<Control> controls = getControls(); 633 if (! controls.isEmpty()) 634 { 635 buffer.append(", controls={"); 636 637 final Iterator<Control> iterator = controls.iterator(); 638 while (iterator.hasNext()) 639 { 640 iterator.next().toString(buffer); 641 if (iterator.hasNext()) 642 { 643 buffer.append(','); 644 } 645 } 646 647 buffer.append('}'); 648 } 649 650 buffer.append(')'); 651 } 652}