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.ldap.sdk; 037 038 039 040import java.util.ArrayList; 041import java.util.List; 042import java.util.Timer; 043import java.util.concurrent.LinkedBlockingQueue; 044import java.util.concurrent.TimeUnit; 045import java.util.logging.Level; 046 047import com.unboundid.asn1.ASN1Boolean; 048import com.unboundid.asn1.ASN1Buffer; 049import com.unboundid.asn1.ASN1BufferSequence; 050import com.unboundid.asn1.ASN1Element; 051import com.unboundid.asn1.ASN1OctetString; 052import com.unboundid.asn1.ASN1Sequence; 053import com.unboundid.ldap.protocol.LDAPMessage; 054import com.unboundid.ldap.protocol.LDAPResponse; 055import com.unboundid.ldap.protocol.ProtocolOp; 056import com.unboundid.ldif.LDIFModifyDNChangeRecord; 057import com.unboundid.util.Debug; 058import com.unboundid.util.InternalUseOnly; 059import com.unboundid.util.Mutable; 060import com.unboundid.util.NotNull; 061import com.unboundid.util.Nullable; 062import com.unboundid.util.StaticUtils; 063import com.unboundid.util.ThreadSafety; 064import com.unboundid.util.ThreadSafetyLevel; 065import com.unboundid.util.Validator; 066 067import static com.unboundid.ldap.sdk.LDAPMessages.*; 068 069 070 071/** 072 * This class implements the processing necessary to perform an LDAPv3 modify DN 073 * operation, which can be used to rename and/or move an entry or subtree in the 074 * directory. A modify DN request contains the DN of the target entry, the new 075 * RDN to use for that entry, and a flag which indicates whether to remove the 076 * current RDN attribute value(s) from the entry. It may optionally contain a 077 * new superior DN, which will cause the entry to be moved below that new parent 078 * entry. 079 * <BR><BR> 080 * Note that some directory servers may not support all possible uses of the 081 * modify DN operation. In particular, some servers may not support the use of 082 * a new superior DN, especially if it may cause the entry to be moved to a 083 * different database or another server. Also, some servers may not support 084 * renaming or moving non-leaf entries (i.e., entries that have one or more 085 * subordinates). 086 * <BR><BR> 087 * {@code ModifyDNRequest} objects are mutable and therefore can be altered and 088 * re-used for multiple requests. Note, however, that {@code ModifyDNRequest} 089 * objects are not threadsafe and therefore a single {@code ModifyDNRequest} 090 * object instance should not be used to process multiple requests at the same 091 * time. 092 * <BR><BR> 093 * <H2>Example</H2> 094 * The following example demonstrates the process for performing a modify DN 095 * operation. In this case, it will rename "ou=People,dc=example,dc=com" to 096 * "ou=Users,dc=example,dc=com". It will not move the entry below a new parent. 097 * <PRE> 098 * ModifyDNRequest modifyDNRequest = 099 * new ModifyDNRequest("ou=People,dc=example,dc=com", "ou=Users", true); 100 * LDAPResult modifyDNResult; 101 * 102 * try 103 * { 104 * modifyDNResult = connection.modifyDN(modifyDNRequest); 105 * // If we get here, the delete was successful. 106 * } 107 * catch (LDAPException le) 108 * { 109 * // The modify DN operation failed. 110 * modifyDNResult = le.toLDAPResult(); 111 * ResultCode resultCode = le.getResultCode(); 112 * String errorMessageFromServer = le.getDiagnosticMessage(); 113 * } 114 * </PRE> 115 */ 116@Mutable() 117@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 118public final class ModifyDNRequest 119 extends UpdatableLDAPRequest 120 implements ReadOnlyModifyDNRequest, ResponseAcceptor, ProtocolOp 121{ 122 /** 123 * The BER type for the new superior element. 124 */ 125 private static final byte NEW_SUPERIOR_TYPE = (byte) 0x80; 126 127 128 129 /** 130 * The serial version UID for this serializable class. 131 */ 132 private static final long serialVersionUID = -2325552729975091008L; 133 134 135 136 // The queue that will be used to receive response messages from the server. 137 @NotNull private final LinkedBlockingQueue<LDAPResponse> responseQueue = 138 new LinkedBlockingQueue<>(); 139 140 // Indicates whether to delete the current RDN value from the entry. 141 private boolean deleteOldRDN; 142 143 // The message ID from the last LDAP message sent from this request. 144 private int messageID = -1; 145 146 // The current DN of the entry to rename. 147 @NotNull private String dn; 148 149 // The new RDN to use for the entry. 150 @NotNull private String newRDN; 151 152 // The new superior DN for the entry. 153 @Nullable private String newSuperiorDN; 154 155 156 157 /** 158 * Creates a new modify DN request that will rename the entry but will not 159 * move it below a new entry. 160 * 161 * @param dn The current DN for the entry to rename. It must not 162 * be {@code null}. 163 * @param newRDN The new RDN for the target entry. It must not be 164 * {@code null}. 165 * @param deleteOldRDN Indicates whether to delete the current RDN value 166 * from the target entry. 167 */ 168 public ModifyDNRequest(@NotNull final String dn, @NotNull final String newRDN, 169 final boolean deleteOldRDN) 170 { 171 super(null); 172 173 Validator.ensureNotNull(dn, newRDN); 174 175 this.dn = dn; 176 this.newRDN = newRDN; 177 this.deleteOldRDN = deleteOldRDN; 178 179 newSuperiorDN = null; 180 } 181 182 183 184 /** 185 * Creates a new modify DN request that will rename the entry but will not 186 * move it below a new entry. 187 * 188 * @param dn The current DN for the entry to rename. It must not 189 * be {@code null}. 190 * @param newRDN The new RDN for the target entry. It must not be 191 * {@code null}. 192 * @param deleteOldRDN Indicates whether to delete the current RDN value 193 * from the target entry. 194 */ 195 public ModifyDNRequest(@NotNull final DN dn, @NotNull final RDN newRDN, 196 final boolean deleteOldRDN) 197 { 198 super(null); 199 200 Validator.ensureNotNull(dn, newRDN); 201 202 this.dn = dn.toString(); 203 this.newRDN = newRDN.toString(); 204 this.deleteOldRDN = deleteOldRDN; 205 206 newSuperiorDN = null; 207 } 208 209 210 211 /** 212 * Creates a new modify DN request that will rename the entry and will 213 * optionally move it below a new entry. 214 * 215 * @param dn The current DN for the entry to rename. It must not 216 * be {@code null}. 217 * @param newRDN The new RDN for the target entry. It must not be 218 * {@code null}. 219 * @param deleteOldRDN Indicates whether to delete the current RDN value 220 * from the target entry. 221 * @param newSuperiorDN The new superior DN for the entry. It may be 222 * {@code null} if the entry is not to be moved below a 223 * new parent. 224 */ 225 public ModifyDNRequest(@NotNull final String dn, @NotNull final String newRDN, 226 final boolean deleteOldRDN, 227 @Nullable final String newSuperiorDN) 228 { 229 super(null); 230 231 Validator.ensureNotNull(dn, newRDN); 232 233 this.dn = dn; 234 this.newRDN = newRDN; 235 this.deleteOldRDN = deleteOldRDN; 236 this.newSuperiorDN = newSuperiorDN; 237 } 238 239 240 241 /** 242 * Creates a new modify DN request that will rename the entry and will 243 * optionally move it below a new entry. 244 * 245 * @param dn The current DN for the entry to rename. It must not 246 * be {@code null}. 247 * @param newRDN The new RDN for the target entry. It must not be 248 * {@code null}. 249 * @param deleteOldRDN Indicates whether to delete the current RDN value 250 * from the target entry. 251 * @param newSuperiorDN The new superior DN for the entry. It may be 252 * {@code null} if the entry is not to be moved below a 253 * new parent. 254 */ 255 public ModifyDNRequest(@NotNull final DN dn, @NotNull final RDN newRDN, 256 final boolean deleteOldRDN, 257 @Nullable final DN newSuperiorDN) 258 { 259 super(null); 260 261 Validator.ensureNotNull(dn, newRDN); 262 263 this.dn = dn.toString(); 264 this.newRDN = newRDN.toString(); 265 this.deleteOldRDN = deleteOldRDN; 266 267 if (newSuperiorDN == null) 268 { 269 this.newSuperiorDN = null; 270 } 271 else 272 { 273 this.newSuperiorDN = newSuperiorDN.toString(); 274 } 275 } 276 277 278 279 /** 280 * Creates a new modify DN request that will rename the entry but will not 281 * move it below a new entry. 282 * 283 * @param dn The current DN for the entry to rename. It must not 284 * be {@code null}. 285 * @param newRDN The new RDN for the target entry. It must not be 286 * {@code null}. 287 * @param deleteOldRDN Indicates whether to delete the current RDN value 288 * from the target entry. 289 * @param controls The set of controls to include in the request. 290 */ 291 public ModifyDNRequest(@NotNull final String dn, @NotNull final String newRDN, 292 final boolean deleteOldRDN, 293 @Nullable final Control[] controls) 294 { 295 super(controls); 296 297 Validator.ensureNotNull(dn, newRDN); 298 299 this.dn = dn; 300 this.newRDN = newRDN; 301 this.deleteOldRDN = deleteOldRDN; 302 303 newSuperiorDN = null; 304 } 305 306 307 308 /** 309 * Creates a new modify DN request that will rename the entry but will not 310 * move it below a new entry. 311 * 312 * @param dn The current DN for the entry to rename. It must not 313 * be {@code null}. 314 * @param newRDN The new RDN for the target entry. It must not be 315 * {@code null}. 316 * @param deleteOldRDN Indicates whether to delete the current RDN value 317 * from the target entry. 318 * @param controls The set of controls to include in the request. 319 */ 320 public ModifyDNRequest(@NotNull final DN dn, @NotNull final RDN newRDN, 321 final boolean deleteOldRDN, 322 @Nullable final Control[] controls) 323 { 324 super(controls); 325 326 Validator.ensureNotNull(dn, newRDN); 327 328 this.dn = dn.toString(); 329 this.newRDN = newRDN.toString(); 330 this.deleteOldRDN = deleteOldRDN; 331 332 newSuperiorDN = null; 333 } 334 335 336 337 /** 338 * Creates a new modify DN request that will rename the entry and will 339 * optionally move it below a new entry. 340 * 341 * @param dn The current DN for the entry to rename. It must not 342 * be {@code null}. 343 * @param newRDN The new RDN for the target entry. It must not be 344 * {@code null}. 345 * @param deleteOldRDN Indicates whether to delete the current RDN value 346 * from the target entry. 347 * @param newSuperiorDN The new superior DN for the entry. It may be 348 * {@code null} if the entry is not to be moved below a 349 * new parent. 350 * @param controls The set of controls to include in the request. 351 */ 352 public ModifyDNRequest(@NotNull final String dn, @NotNull final String newRDN, 353 final boolean deleteOldRDN, 354 @Nullable final String newSuperiorDN, 355 @Nullable final Control[] controls) 356 { 357 super(controls); 358 359 Validator.ensureNotNull(dn, newRDN); 360 361 this.dn = dn; 362 this.newRDN = newRDN; 363 this.deleteOldRDN = deleteOldRDN; 364 this.newSuperiorDN = newSuperiorDN; 365 } 366 367 368 369 /** 370 * Creates a new modify DN request that will rename the entry and will 371 * optionally move it below a new entry. 372 * 373 * @param dn The current DN for the entry to rename. It must not 374 * be {@code null}. 375 * @param newRDN The new RDN for the target entry. It must not be 376 * {@code null}. 377 * @param deleteOldRDN Indicates whether to delete the current RDN value 378 * from the target entry. 379 * @param newSuperiorDN The new superior DN for the entry. It may be 380 * {@code null} if the entry is not to be moved below a 381 * new parent. 382 * @param controls The set of controls to include in the request. 383 */ 384 public ModifyDNRequest(@NotNull final DN dn, @NotNull final RDN newRDN, 385 final boolean deleteOldRDN, 386 @Nullable final DN newSuperiorDN, 387 @Nullable final Control[] controls) 388 { 389 super(controls); 390 391 Validator.ensureNotNull(dn, newRDN); 392 393 this.dn = dn.toString(); 394 this.newRDN = newRDN.toString(); 395 this.deleteOldRDN = deleteOldRDN; 396 397 if (newSuperiorDN == null) 398 { 399 this.newSuperiorDN = null; 400 } 401 else 402 { 403 this.newSuperiorDN = newSuperiorDN.toString(); 404 } 405 } 406 407 408 409 /** 410 * {@inheritDoc} 411 */ 412 @Override() 413 @NotNull() 414 public String getDN() 415 { 416 return dn; 417 } 418 419 420 421 /** 422 * Specifies the current DN of the entry to move/rename. 423 * 424 * @param dn The current DN of the entry to move/rename. It must not be 425 * {@code null}. 426 */ 427 public void setDN(@NotNull final String dn) 428 { 429 Validator.ensureNotNull(dn); 430 431 this.dn = dn; 432 } 433 434 435 436 /** 437 * Specifies the current DN of the entry to move/rename. 438 * 439 * @param dn The current DN of the entry to move/rename. It must not be 440 * {@code null}. 441 */ 442 public void setDN(@NotNull final DN dn) 443 { 444 Validator.ensureNotNull(dn); 445 446 this.dn = dn.toString(); 447 } 448 449 450 451 /** 452 * {@inheritDoc} 453 */ 454 @Override() 455 @NotNull() 456 public String getNewRDN() 457 { 458 return newRDN; 459 } 460 461 462 463 /** 464 * Specifies the new RDN for the entry. 465 * 466 * @param newRDN The new RDN for the entry. It must not be {@code null}. 467 */ 468 public void setNewRDN(@NotNull final String newRDN) 469 { 470 Validator.ensureNotNull(newRDN); 471 472 this.newRDN = newRDN; 473 } 474 475 476 477 /** 478 * Specifies the new RDN for the entry. 479 * 480 * @param newRDN The new RDN for the entry. It must not be {@code null}. 481 */ 482 public void setNewRDN(@NotNull final RDN newRDN) 483 { 484 Validator.ensureNotNull(newRDN); 485 486 this.newRDN = newRDN.toString(); 487 } 488 489 490 491 /** 492 * {@inheritDoc} 493 */ 494 @Override() 495 public boolean deleteOldRDN() 496 { 497 return deleteOldRDN; 498 } 499 500 501 502 /** 503 * Specifies whether the current RDN value should be removed from the entry. 504 * 505 * @param deleteOldRDN Specifies whether the current RDN value should be 506 * removed from the entry. 507 */ 508 public void setDeleteOldRDN(final boolean deleteOldRDN) 509 { 510 this.deleteOldRDN = deleteOldRDN; 511 } 512 513 514 515 /** 516 * {@inheritDoc} 517 */ 518 @Override() 519 @Nullable() 520 public String getNewSuperiorDN() 521 { 522 return newSuperiorDN; 523 } 524 525 526 527 /** 528 * Specifies the new superior DN for the entry. 529 * 530 * @param newSuperiorDN The new superior DN for the entry. It may be 531 * {@code null} if the entry is not to be removed below 532 * a new parent. 533 */ 534 public void setNewSuperiorDN(@Nullable final String newSuperiorDN) 535 { 536 this.newSuperiorDN = newSuperiorDN; 537 } 538 539 540 541 /** 542 * Specifies the new superior DN for the entry. 543 * 544 * @param newSuperiorDN The new superior DN for the entry. It may be 545 * {@code null} if the entry is not to be removed below 546 * a new parent. 547 */ 548 public void setNewSuperiorDN(@Nullable final DN newSuperiorDN) 549 { 550 if (newSuperiorDN == null) 551 { 552 this.newSuperiorDN = null; 553 } 554 else 555 { 556 this.newSuperiorDN = newSuperiorDN.toString(); 557 } 558 } 559 560 561 562 /** 563 * {@inheritDoc} 564 */ 565 @Override() 566 public byte getProtocolOpType() 567 { 568 return LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST; 569 } 570 571 572 573 /** 574 * {@inheritDoc} 575 */ 576 @Override() 577 public void writeTo(@NotNull final ASN1Buffer writer) 578 { 579 final ASN1BufferSequence requestSequence = 580 writer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST); 581 writer.addOctetString(dn); 582 writer.addOctetString(newRDN); 583 writer.addBoolean(deleteOldRDN); 584 585 if (newSuperiorDN != null) 586 { 587 writer.addOctetString(NEW_SUPERIOR_TYPE, newSuperiorDN); 588 } 589 requestSequence.end(); 590 } 591 592 593 594 /** 595 * Encodes the modify DN request protocol op to an ASN.1 element. 596 * 597 * @return The ASN.1 element with the encoded modify DN request protocol op. 598 */ 599 @Override() 600 @NotNull() 601 public ASN1Element encodeProtocolOp() 602 { 603 final ASN1Element[] protocolOpElements; 604 if (newSuperiorDN == null) 605 { 606 protocolOpElements = new ASN1Element[] 607 { 608 new ASN1OctetString(dn), 609 new ASN1OctetString(newRDN), 610 new ASN1Boolean(deleteOldRDN) 611 }; 612 } 613 else 614 { 615 protocolOpElements = new ASN1Element[] 616 { 617 new ASN1OctetString(dn), 618 new ASN1OctetString(newRDN), 619 new ASN1Boolean(deleteOldRDN), 620 new ASN1OctetString(NEW_SUPERIOR_TYPE, newSuperiorDN) 621 }; 622 } 623 624 return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST, 625 protocolOpElements); 626 } 627 628 629 630 /** 631 * Sends this modify DN request to the directory server over the provided 632 * connection and returns the associated response. 633 * 634 * @param connection The connection to use to communicate with the directory 635 * server. 636 * @param depth The current referral depth for this request. It should 637 * always be one for the initial request, and should only 638 * be incremented when following referrals. 639 * 640 * @return An LDAP result object that provides information about the result 641 * of the modify DN processing. 642 * 643 * @throws LDAPException If a problem occurs while sending the request or 644 * reading the response. 645 */ 646 @Override() 647 @NotNull() 648 protected LDAPResult process(@NotNull final LDAPConnection connection, 649 final int depth) 650 throws LDAPException 651 { 652 setReferralDepth(depth); 653 654 if (connection.synchronousMode()) 655 { 656 @SuppressWarnings("deprecation") 657 final boolean autoReconnect = 658 connection.getConnectionOptions().autoReconnect(); 659 return processSync(connection, depth, autoReconnect); 660 } 661 662 final long requestTime = System.nanoTime(); 663 processAsync(connection, null); 664 665 try 666 { 667 // Wait for and process the response. 668 final LDAPResponse response; 669 try 670 { 671 final long responseTimeout = getResponseTimeoutMillis(connection); 672 if (responseTimeout > 0) 673 { 674 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS); 675 } 676 else 677 { 678 response = responseQueue.take(); 679 } 680 } 681 catch (final InterruptedException ie) 682 { 683 Debug.debugException(ie); 684 Thread.currentThread().interrupt(); 685 throw new LDAPException(ResultCode.LOCAL_ERROR, 686 ERR_MODDN_INTERRUPTED.get(connection.getHostPort()), ie); 687 } 688 689 return handleResponse(connection, response, requestTime, depth, false); 690 } 691 finally 692 { 693 connection.deregisterResponseAcceptor(messageID); 694 } 695 } 696 697 698 699 /** 700 * Sends this modify DN request to the directory server over the provided 701 * connection and returns the message ID for the request. 702 * 703 * @param connection The connection to use to communicate with the 704 * directory server. 705 * @param resultListener The async result listener that is to be notified 706 * when the response is received. It may be 707 * {@code null} only if the result is to be processed 708 * by this class. 709 * 710 * @return The async request ID created for the operation, or {@code null} if 711 * the provided {@code resultListener} is {@code null} and the 712 * operation will not actually be processed asynchronously. 713 * 714 * @throws LDAPException If a problem occurs while sending the request. 715 */ 716 @Nullable() 717 AsyncRequestID processAsync(@NotNull final LDAPConnection connection, 718 @Nullable final AsyncResultListener resultListener) 719 throws LDAPException 720 { 721 // Create the LDAP message. 722 messageID = connection.nextMessageID(); 723 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 724 725 726 // If the provided async result listener is {@code null}, then we'll use 727 // this class as the message acceptor. Otherwise, create an async helper 728 // and use it as the message acceptor. 729 final AsyncRequestID asyncRequestID; 730 final long timeout = getResponseTimeoutMillis(connection); 731 if (resultListener == null) 732 { 733 asyncRequestID = null; 734 connection.registerResponseAcceptor(messageID, this); 735 } 736 else 737 { 738 final AsyncHelper helper = new AsyncHelper(connection, 739 OperationType.MODIFY_DN, messageID, resultListener, 740 getIntermediateResponseListener()); 741 connection.registerResponseAcceptor(messageID, helper); 742 asyncRequestID = helper.getAsyncRequestID(); 743 744 if (timeout > 0L) 745 { 746 final Timer timer = connection.getTimer(); 747 final AsyncTimeoutTimerTask timerTask = 748 new AsyncTimeoutTimerTask(helper); 749 timer.schedule(timerTask, timeout); 750 asyncRequestID.setTimerTask(timerTask); 751 } 752 } 753 754 755 // Send the request to the server. 756 try 757 { 758 Debug.debugLDAPRequest(Level.INFO, this, messageID, connection); 759 760 final LDAPConnectionLogger logger = 761 connection.getConnectionOptions().getConnectionLogger(); 762 if (logger != null) 763 { 764 logger.logModifyDNRequest(connection, messageID, this); 765 } 766 767 connection.getConnectionStatistics().incrementNumModifyDNRequests(); 768 connection.sendMessage(message, timeout); 769 return asyncRequestID; 770 } 771 catch (final LDAPException le) 772 { 773 Debug.debugException(le); 774 775 connection.deregisterResponseAcceptor(messageID); 776 throw le; 777 } 778 } 779 780 781 782 /** 783 * Processes this modify DN operation in synchronous mode, in which the same 784 * thread will send the request and read the response. 785 * 786 * @param connection The connection to use to communicate with the directory 787 * server. 788 * @param depth The current referral depth for this request. It should 789 * always be one for the initial request, and should only 790 * be incremented when following referrals. 791 * @param allowRetry Indicates whether the request may be re-tried on a 792 * re-established connection if the initial attempt fails 793 * in a way that indicates the connection is no longer 794 * valid and autoReconnect is true. 795 * 796 * @return An LDAP result object that provides information about the result 797 * of the modify DN processing. 798 * 799 * @throws LDAPException If a problem occurs while sending the request or 800 * reading the response. 801 */ 802 @NotNull() 803 private LDAPResult processSync(@NotNull final LDAPConnection connection, 804 final int depth, 805 final boolean allowRetry) 806 throws LDAPException 807 { 808 // Create the LDAP message. 809 messageID = connection.nextMessageID(); 810 final LDAPMessage message = 811 new LDAPMessage(messageID, this, getControls()); 812 813 814 // Send the request to the server. 815 final long requestTime = System.nanoTime(); 816 Debug.debugLDAPRequest(Level.INFO, this, messageID, connection); 817 818 final LDAPConnectionLogger logger = 819 connection.getConnectionOptions().getConnectionLogger(); 820 if (logger != null) 821 { 822 logger.logModifyDNRequest(connection, messageID, this); 823 } 824 825 connection.getConnectionStatistics().incrementNumModifyDNRequests(); 826 try 827 { 828 connection.sendMessage(message, getResponseTimeoutMillis(connection)); 829 } 830 catch (final LDAPException le) 831 { 832 Debug.debugException(le); 833 834 if (allowRetry) 835 { 836 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 837 le.getResultCode()); 838 if (retryResult != null) 839 { 840 return retryResult; 841 } 842 } 843 844 throw le; 845 } 846 847 while (true) 848 { 849 final LDAPResponse response; 850 try 851 { 852 response = connection.readResponse(messageID); 853 } 854 catch (final LDAPException le) 855 { 856 Debug.debugException(le); 857 858 if ((le.getResultCode() == ResultCode.TIMEOUT) && 859 connection.getConnectionOptions().abandonOnTimeout()) 860 { 861 connection.abandon(messageID); 862 } 863 864 if (allowRetry) 865 { 866 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 867 le.getResultCode()); 868 if (retryResult != null) 869 { 870 return retryResult; 871 } 872 } 873 874 throw le; 875 } 876 877 if (response instanceof IntermediateResponse) 878 { 879 final IntermediateResponseListener listener = 880 getIntermediateResponseListener(); 881 if (listener != null) 882 { 883 listener.intermediateResponseReturned( 884 (IntermediateResponse) response); 885 } 886 } 887 else 888 { 889 return handleResponse(connection, response, requestTime, depth, 890 allowRetry); 891 } 892 } 893 } 894 895 896 897 /** 898 * Performs the necessary processing for handling a response. 899 * 900 * @param connection The connection used to read the response. 901 * @param response The response to be processed. 902 * @param requestTime The time the request was sent to the server. 903 * @param depth The current referral depth for this request. It 904 * should always be one for the initial request, and 905 * should only be incremented when following referrals. 906 * @param allowRetry Indicates whether the request may be re-tried on a 907 * re-established connection if the initial attempt fails 908 * in a way that indicates the connection is no longer 909 * valid and autoReconnect is true. 910 * 911 * @return The modify DN result. 912 * 913 * @throws LDAPException If a problem occurs. 914 */ 915 @NotNull() 916 private LDAPResult handleResponse(@NotNull final LDAPConnection connection, 917 @Nullable final LDAPResponse response, 918 final long requestTime, final int depth, 919 final boolean allowRetry) 920 throws LDAPException 921 { 922 if (response == null) 923 { 924 final long waitTime = 925 StaticUtils.nanosToMillis(System.nanoTime() - requestTime); 926 if (connection.getConnectionOptions().abandonOnTimeout()) 927 { 928 connection.abandon(messageID); 929 } 930 931 throw new LDAPException(ResultCode.TIMEOUT, 932 ERR_MODIFY_DN_CLIENT_TIMEOUT.get(waitTime, messageID, dn, 933 connection.getHostPort())); 934 } 935 936 connection.getConnectionStatistics().incrementNumModifyDNResponses( 937 System.nanoTime() - requestTime); 938 if (response instanceof ConnectionClosedResponse) 939 { 940 // The connection was closed while waiting for the response. 941 if (allowRetry) 942 { 943 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 944 ResultCode.SERVER_DOWN); 945 if (retryResult != null) 946 { 947 return retryResult; 948 } 949 } 950 951 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 952 final String message = ccr.getMessage(); 953 if (message == null) 954 { 955 throw new LDAPException(ccr.getResultCode(), 956 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_DN_RESPONSE.get( 957 connection.getHostPort(), toString())); 958 } 959 else 960 { 961 throw new LDAPException(ccr.getResultCode(), 962 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_DN_RESPONSE_WITH_MESSAGE.get( 963 connection.getHostPort(), toString(), message)); 964 } 965 } 966 967 final LDAPResult result = (LDAPResult) response; 968 if ((result.getResultCode().equals(ResultCode.REFERRAL)) && 969 followReferrals(connection)) 970 { 971 if (depth >= connection.getConnectionOptions().getReferralHopLimit()) 972 { 973 return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED, 974 ERR_TOO_MANY_REFERRALS.get(), 975 result.getMatchedDN(), result.getReferralURLs(), 976 result.getResponseControls()); 977 } 978 979 return ReferralHelper.handleReferral(this, result, connection); 980 } 981 else 982 { 983 if (allowRetry) 984 { 985 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 986 result.getResultCode()); 987 if (retryResult != null) 988 { 989 return retryResult; 990 } 991 } 992 993 return result; 994 } 995 } 996 997 998 999 /** 1000 * Attempts to re-establish the connection and retry processing this request 1001 * on it. 1002 * 1003 * @param connection The connection to be re-established. 1004 * @param depth The current referral depth for this request. It should 1005 * always be one for the initial request, and should only 1006 * be incremented when following referrals. 1007 * @param resultCode The result code for the previous operation attempt. 1008 * 1009 * @return The result from re-trying the add, or {@code null} if it could not 1010 * be re-tried. 1011 */ 1012 @Nullable() 1013 private LDAPResult reconnectAndRetry(@NotNull final LDAPConnection connection, 1014 final int depth, 1015 @NotNull final ResultCode resultCode) 1016 { 1017 try 1018 { 1019 // We will only want to retry for certain result codes that indicate a 1020 // connection problem. 1021 switch (resultCode.intValue()) 1022 { 1023 case ResultCode.SERVER_DOWN_INT_VALUE: 1024 case ResultCode.DECODING_ERROR_INT_VALUE: 1025 case ResultCode.CONNECT_ERROR_INT_VALUE: 1026 connection.reconnect(); 1027 return processSync(connection, depth, false); 1028 } 1029 } 1030 catch (final Exception e) 1031 { 1032 Debug.debugException(e); 1033 } 1034 1035 return null; 1036 } 1037 1038 1039 1040 /** 1041 * {@inheritDoc} 1042 */ 1043 @InternalUseOnly() 1044 @Override() 1045 public void responseReceived(@NotNull final LDAPResponse response) 1046 throws LDAPException 1047 { 1048 try 1049 { 1050 responseQueue.put(response); 1051 } 1052 catch (final Exception e) 1053 { 1054 Debug.debugException(e); 1055 1056 if (e instanceof InterruptedException) 1057 { 1058 Thread.currentThread().interrupt(); 1059 } 1060 1061 throw new LDAPException(ResultCode.LOCAL_ERROR, 1062 ERR_EXCEPTION_HANDLING_RESPONSE.get( 1063 StaticUtils.getExceptionMessage(e)), e); 1064 } 1065 } 1066 1067 1068 1069 /** 1070 * {@inheritDoc} 1071 */ 1072 @Override() 1073 public int getLastMessageID() 1074 { 1075 return messageID; 1076 } 1077 1078 1079 1080 /** 1081 * {@inheritDoc} 1082 */ 1083 @Override() 1084 @NotNull() 1085 public OperationType getOperationType() 1086 { 1087 return OperationType.MODIFY_DN; 1088 } 1089 1090 1091 1092 /** 1093 * {@inheritDoc} 1094 */ 1095 @Override() 1096 @NotNull() 1097 public ModifyDNRequest duplicate() 1098 { 1099 return duplicate(getControls()); 1100 } 1101 1102 1103 1104 /** 1105 * {@inheritDoc} 1106 */ 1107 @Override() 1108 @NotNull() 1109 public ModifyDNRequest duplicate(@NotNull final Control[] controls) 1110 { 1111 final ModifyDNRequest r = new ModifyDNRequest(dn, newRDN, deleteOldRDN, 1112 newSuperiorDN, controls); 1113 1114 if (followReferralsInternal() != null) 1115 { 1116 r.setFollowReferrals(followReferralsInternal()); 1117 } 1118 1119 if (getReferralConnectorInternal() != null) 1120 { 1121 r.setReferralConnector(getReferralConnectorInternal()); 1122 } 1123 1124 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 1125 r.setIntermediateResponseListener(getIntermediateResponseListener()); 1126 r.setReferralDepth(getReferralDepth()); 1127 1128 return r; 1129 } 1130 1131 1132 1133 /** 1134 * {@inheritDoc} 1135 */ 1136 @Override() 1137 @NotNull() 1138 public LDIFModifyDNChangeRecord toLDIFChangeRecord() 1139 { 1140 return new LDIFModifyDNChangeRecord(this); 1141 } 1142 1143 1144 1145 /** 1146 * {@inheritDoc} 1147 */ 1148 @Override() 1149 @NotNull() 1150 public String[] toLDIF() 1151 { 1152 return toLDIFChangeRecord().toLDIF(); 1153 } 1154 1155 1156 1157 /** 1158 * {@inheritDoc} 1159 */ 1160 @Override() 1161 @NotNull() 1162 public String toLDIFString() 1163 { 1164 return toLDIFChangeRecord().toLDIFString(); 1165 } 1166 1167 1168 1169 /** 1170 * {@inheritDoc} 1171 */ 1172 @Override() 1173 public void toString(@NotNull final StringBuilder buffer) 1174 { 1175 buffer.append("ModifyDNRequest(dn='"); 1176 buffer.append(dn); 1177 buffer.append("', newRDN='"); 1178 buffer.append(newRDN); 1179 buffer.append("', deleteOldRDN="); 1180 buffer.append(deleteOldRDN); 1181 1182 if (newSuperiorDN != null) 1183 { 1184 buffer.append(", newSuperiorDN='"); 1185 buffer.append(newSuperiorDN); 1186 buffer.append('\''); 1187 } 1188 1189 final Control[] controls = getControls(); 1190 if (controls.length > 0) 1191 { 1192 buffer.append(", controls={"); 1193 for (int i=0; i < controls.length; i++) 1194 { 1195 if (i > 0) 1196 { 1197 buffer.append(", "); 1198 } 1199 1200 buffer.append(controls[i]); 1201 } 1202 buffer.append('}'); 1203 } 1204 1205 buffer.append(')'); 1206 } 1207 1208 1209 1210 /** 1211 * {@inheritDoc} 1212 */ 1213 @Override() 1214 public void toCode(@NotNull final List<String> lineList, 1215 @NotNull final String requestID, 1216 final int indentSpaces, final boolean includeProcessing) 1217 { 1218 // Create the request variable. 1219 final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(4); 1220 constructorArgs.add(ToCodeArgHelper.createString(dn, "Current DN")); 1221 constructorArgs.add(ToCodeArgHelper.createString(newRDN, "New RDN")); 1222 constructorArgs.add(ToCodeArgHelper.createBoolean(deleteOldRDN, 1223 "Delete Old RDN Value(s)")); 1224 1225 if (newSuperiorDN != null) 1226 { 1227 constructorArgs.add(ToCodeArgHelper.createString(newSuperiorDN, 1228 "New Superior Entry DN")); 1229 } 1230 1231 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ModifyDNRequest", 1232 requestID + "Request", "new ModifyDNRequest", constructorArgs); 1233 1234 1235 // If there are any controls, then add them to the request. 1236 for (final Control c : getControls()) 1237 { 1238 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1239 requestID + "Request.addControl", 1240 ToCodeArgHelper.createControl(c, null)); 1241 } 1242 1243 1244 // Add lines for processing the request and obtaining the result. 1245 if (includeProcessing) 1246 { 1247 // Generate a string with the appropriate indent. 1248 final StringBuilder buffer = new StringBuilder(); 1249 for (int i=0; i < indentSpaces; i++) 1250 { 1251 buffer.append(' '); 1252 } 1253 final String indent = buffer.toString(); 1254 1255 lineList.add(""); 1256 lineList.add(indent + "try"); 1257 lineList.add(indent + '{'); 1258 lineList.add(indent + " LDAPResult " + requestID + 1259 "Result = connection.modifyDN(" + requestID + "Request);"); 1260 lineList.add(indent + " // The modify DN was processed successfully."); 1261 lineList.add(indent + '}'); 1262 lineList.add(indent + "catch (LDAPException e)"); 1263 lineList.add(indent + '{'); 1264 lineList.add(indent + " // The modify DN failed. Maybe the following " + 1265 "will help explain why."); 1266 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 1267 lineList.add(indent + " String message = e.getMessage();"); 1268 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 1269 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 1270 lineList.add(indent + " Control[] responseControls = " + 1271 "e.getResponseControls();"); 1272 lineList.add(indent + '}'); 1273 } 1274 } 1275}