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