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