001/* 002 * Copyright 2007-2022 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2022 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-2022 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 if (connection.synchronousMode()) 653 { 654 @SuppressWarnings("deprecation") 655 final boolean autoReconnect = 656 connection.getConnectionOptions().autoReconnect(); 657 return processSync(connection, depth, autoReconnect); 658 } 659 660 final long requestTime = System.nanoTime(); 661 processAsync(connection, null); 662 663 try 664 { 665 // Wait for and process the response. 666 final LDAPResponse response; 667 try 668 { 669 final long responseTimeout = getResponseTimeoutMillis(connection); 670 if (responseTimeout > 0) 671 { 672 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS); 673 } 674 else 675 { 676 response = responseQueue.take(); 677 } 678 } 679 catch (final InterruptedException ie) 680 { 681 Debug.debugException(ie); 682 Thread.currentThread().interrupt(); 683 throw new LDAPException(ResultCode.LOCAL_ERROR, 684 ERR_MODDN_INTERRUPTED.get(connection.getHostPort()), ie); 685 } 686 687 return handleResponse(connection, response, requestTime, depth, false); 688 } 689 finally 690 { 691 connection.deregisterResponseAcceptor(messageID); 692 } 693 } 694 695 696 697 /** 698 * Sends this modify DN request to the directory server over the provided 699 * connection and returns the message ID for the request. 700 * 701 * @param connection The connection to use to communicate with the 702 * directory server. 703 * @param resultListener The async result listener that is to be notified 704 * when the response is received. It may be 705 * {@code null} only if the result is to be processed 706 * by this class. 707 * 708 * @return The async request ID created for the operation, or {@code null} if 709 * the provided {@code resultListener} is {@code null} and the 710 * operation will not actually be processed asynchronously. 711 * 712 * @throws LDAPException If a problem occurs while sending the request. 713 */ 714 @Nullable() 715 AsyncRequestID processAsync(@NotNull final LDAPConnection connection, 716 @Nullable final AsyncResultListener resultListener) 717 throws LDAPException 718 { 719 // Create the LDAP message. 720 messageID = connection.nextMessageID(); 721 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 722 723 724 // If the provided async result listener is {@code null}, then we'll use 725 // this class as the message acceptor. Otherwise, create an async helper 726 // and use it as the message acceptor. 727 final AsyncRequestID asyncRequestID; 728 final long timeout = getResponseTimeoutMillis(connection); 729 if (resultListener == null) 730 { 731 asyncRequestID = null; 732 connection.registerResponseAcceptor(messageID, this); 733 } 734 else 735 { 736 final AsyncHelper helper = new AsyncHelper(connection, 737 OperationType.MODIFY_DN, messageID, resultListener, 738 getIntermediateResponseListener()); 739 connection.registerResponseAcceptor(messageID, helper); 740 asyncRequestID = helper.getAsyncRequestID(); 741 742 if (timeout > 0L) 743 { 744 final Timer timer = connection.getTimer(); 745 final AsyncTimeoutTimerTask timerTask = 746 new AsyncTimeoutTimerTask(helper); 747 timer.schedule(timerTask, timeout); 748 asyncRequestID.setTimerTask(timerTask); 749 } 750 } 751 752 753 // Send the request to the server. 754 try 755 { 756 Debug.debugLDAPRequest(Level.INFO, this, messageID, connection); 757 758 final LDAPConnectionLogger logger = 759 connection.getConnectionOptions().getConnectionLogger(); 760 if (logger != null) 761 { 762 logger.logModifyDNRequest(connection, messageID, this); 763 } 764 765 connection.getConnectionStatistics().incrementNumModifyDNRequests(); 766 connection.sendMessage(message, timeout); 767 return asyncRequestID; 768 } 769 catch (final LDAPException le) 770 { 771 Debug.debugException(le); 772 773 connection.deregisterResponseAcceptor(messageID); 774 throw le; 775 } 776 } 777 778 779 780 /** 781 * Processes this modify DN operation in synchronous mode, in which the same 782 * thread will send the request and read the response. 783 * 784 * @param connection The connection to use to communicate with the directory 785 * server. 786 * @param depth The current referral depth for this request. It should 787 * always be one for the initial request, and should only 788 * be incremented when following referrals. 789 * @param allowRetry Indicates whether the request may be re-tried on a 790 * re-established connection if the initial attempt fails 791 * in a way that indicates the connection is no longer 792 * valid and autoReconnect is true. 793 * 794 * @return An LDAP result object that provides information about the result 795 * of the modify DN processing. 796 * 797 * @throws LDAPException If a problem occurs while sending the request or 798 * reading the response. 799 */ 800 @NotNull() 801 private LDAPResult processSync(@NotNull final LDAPConnection connection, 802 final int depth, 803 final boolean allowRetry) 804 throws LDAPException 805 { 806 // Create the LDAP message. 807 messageID = connection.nextMessageID(); 808 final LDAPMessage message = 809 new LDAPMessage(messageID, this, getControls()); 810 811 812 // Send the request to the server. 813 final long requestTime = System.nanoTime(); 814 Debug.debugLDAPRequest(Level.INFO, this, messageID, connection); 815 816 final LDAPConnectionLogger logger = 817 connection.getConnectionOptions().getConnectionLogger(); 818 if (logger != null) 819 { 820 logger.logModifyDNRequest(connection, messageID, this); 821 } 822 823 connection.getConnectionStatistics().incrementNumModifyDNRequests(); 824 try 825 { 826 connection.sendMessage(message, getResponseTimeoutMillis(connection)); 827 } 828 catch (final LDAPException le) 829 { 830 Debug.debugException(le); 831 832 if (allowRetry) 833 { 834 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 835 le.getResultCode()); 836 if (retryResult != null) 837 { 838 return retryResult; 839 } 840 } 841 842 throw le; 843 } 844 845 while (true) 846 { 847 final LDAPResponse response; 848 try 849 { 850 response = connection.readResponse(messageID); 851 } 852 catch (final LDAPException le) 853 { 854 Debug.debugException(le); 855 856 if ((le.getResultCode() == ResultCode.TIMEOUT) && 857 connection.getConnectionOptions().abandonOnTimeout()) 858 { 859 connection.abandon(messageID); 860 } 861 862 if (allowRetry) 863 { 864 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 865 le.getResultCode()); 866 if (retryResult != null) 867 { 868 return retryResult; 869 } 870 } 871 872 throw le; 873 } 874 875 if (response instanceof IntermediateResponse) 876 { 877 final IntermediateResponseListener listener = 878 getIntermediateResponseListener(); 879 if (listener != null) 880 { 881 listener.intermediateResponseReturned( 882 (IntermediateResponse) response); 883 } 884 } 885 else 886 { 887 return handleResponse(connection, response, requestTime, depth, 888 allowRetry); 889 } 890 } 891 } 892 893 894 895 /** 896 * Performs the necessary processing for handling a response. 897 * 898 * @param connection The connection used to read the response. 899 * @param response The response to be processed. 900 * @param requestTime The time the request was sent to the server. 901 * @param depth The current referral depth for this request. It 902 * should always be one for the initial request, and 903 * should only be incremented when following referrals. 904 * @param allowRetry Indicates whether the request may be re-tried on a 905 * re-established connection if the initial attempt fails 906 * in a way that indicates the connection is no longer 907 * valid and autoReconnect is true. 908 * 909 * @return The modify DN result. 910 * 911 * @throws LDAPException If a problem occurs. 912 */ 913 @NotNull() 914 private LDAPResult handleResponse(@NotNull final LDAPConnection connection, 915 @Nullable final LDAPResponse response, 916 final long requestTime, final int depth, 917 final boolean allowRetry) 918 throws LDAPException 919 { 920 if (response == null) 921 { 922 final long waitTime = 923 StaticUtils.nanosToMillis(System.nanoTime() - requestTime); 924 if (connection.getConnectionOptions().abandonOnTimeout()) 925 { 926 connection.abandon(messageID); 927 } 928 929 throw new LDAPException(ResultCode.TIMEOUT, 930 ERR_MODIFY_DN_CLIENT_TIMEOUT.get(waitTime, messageID, dn, 931 connection.getHostPort())); 932 } 933 934 connection.getConnectionStatistics().incrementNumModifyDNResponses( 935 System.nanoTime() - requestTime); 936 if (response instanceof ConnectionClosedResponse) 937 { 938 // The connection was closed while waiting for the response. 939 if (allowRetry) 940 { 941 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 942 ResultCode.SERVER_DOWN); 943 if (retryResult != null) 944 { 945 return retryResult; 946 } 947 } 948 949 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 950 final String message = ccr.getMessage(); 951 if (message == null) 952 { 953 throw new LDAPException(ccr.getResultCode(), 954 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_DN_RESPONSE.get( 955 connection.getHostPort(), toString())); 956 } 957 else 958 { 959 throw new LDAPException(ccr.getResultCode(), 960 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_DN_RESPONSE_WITH_MESSAGE.get( 961 connection.getHostPort(), toString(), message)); 962 } 963 } 964 965 final LDAPResult result = (LDAPResult) response; 966 if ((result.getResultCode().equals(ResultCode.REFERRAL)) && 967 followReferrals(connection)) 968 { 969 if (depth >= connection.getConnectionOptions().getReferralHopLimit()) 970 { 971 return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED, 972 ERR_TOO_MANY_REFERRALS.get(), 973 result.getMatchedDN(), result.getReferralURLs(), 974 result.getResponseControls()); 975 } 976 977 return followReferral(result, connection, depth); 978 } 979 else 980 { 981 if (allowRetry) 982 { 983 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 984 result.getResultCode()); 985 if (retryResult != null) 986 { 987 return retryResult; 988 } 989 } 990 991 return result; 992 } 993 } 994 995 996 997 /** 998 * Attempts to re-establish the connection and retry processing this request 999 * on it. 1000 * 1001 * @param connection The connection to be re-established. 1002 * @param depth The current referral depth for this request. It should 1003 * always be one for the initial request, and should only 1004 * be incremented when following referrals. 1005 * @param resultCode The result code for the previous operation attempt. 1006 * 1007 * @return The result from re-trying the add, or {@code null} if it could not 1008 * be re-tried. 1009 */ 1010 @Nullable() 1011 private LDAPResult reconnectAndRetry(@NotNull final LDAPConnection connection, 1012 final int depth, 1013 @NotNull final ResultCode resultCode) 1014 { 1015 try 1016 { 1017 // We will only want to retry for certain result codes that indicate a 1018 // connection problem. 1019 switch (resultCode.intValue()) 1020 { 1021 case ResultCode.SERVER_DOWN_INT_VALUE: 1022 case ResultCode.DECODING_ERROR_INT_VALUE: 1023 case ResultCode.CONNECT_ERROR_INT_VALUE: 1024 connection.reconnect(); 1025 return processSync(connection, depth, false); 1026 } 1027 } 1028 catch (final Exception e) 1029 { 1030 Debug.debugException(e); 1031 } 1032 1033 return null; 1034 } 1035 1036 1037 1038 /** 1039 * Attempts to follow a referral to perform a modify DN operation in the 1040 * target server. 1041 * 1042 * @param referralResult The LDAP result object containing information about 1043 * the referral to follow. 1044 * @param connection The connection on which the referral was received. 1045 * @param depth The number of referrals followed in the course of 1046 * processing this request. 1047 * 1048 * @return The result of attempting to process the modify DN operation by 1049 * following the referral. 1050 * 1051 * @throws LDAPException If a problem occurs while attempting to establish 1052 * the referral connection, sending the request, or 1053 * reading the result. 1054 */ 1055 @NotNull() 1056 private LDAPResult followReferral(@NotNull final LDAPResult referralResult, 1057 @NotNull final LDAPConnection connection, 1058 final int depth) 1059 throws LDAPException 1060 { 1061 for (final String urlString : referralResult.getReferralURLs()) 1062 { 1063 try 1064 { 1065 final LDAPURL referralURL = new LDAPURL(urlString); 1066 final String host = referralURL.getHost(); 1067 1068 if (host == null) 1069 { 1070 // We can't handle a referral in which there is no host. 1071 continue; 1072 } 1073 1074 final ModifyDNRequest modifyDNRequest; 1075 if (referralURL.baseDNProvided()) 1076 { 1077 modifyDNRequest = 1078 new ModifyDNRequest(referralURL.getBaseDN().toString(), 1079 newRDN, deleteOldRDN, newSuperiorDN, 1080 getControls()); 1081 } 1082 else 1083 { 1084 modifyDNRequest = this; 1085 } 1086 1087 final LDAPConnection referralConn = getReferralConnector(connection). 1088 getReferralConnection(referralURL, connection); 1089 try 1090 { 1091 return modifyDNRequest.process(referralConn, depth+1); 1092 } 1093 finally 1094 { 1095 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null); 1096 referralConn.close(); 1097 } 1098 } 1099 catch (final LDAPException le) 1100 { 1101 Debug.debugException(le); 1102 } 1103 } 1104 1105 // If we've gotten here, then we could not follow any of the referral URLs, 1106 // so we'll just return the original referral result. 1107 return referralResult; 1108 } 1109 1110 1111 1112 /** 1113 * {@inheritDoc} 1114 */ 1115 @InternalUseOnly() 1116 @Override() 1117 public void responseReceived(@NotNull final LDAPResponse response) 1118 throws LDAPException 1119 { 1120 try 1121 { 1122 responseQueue.put(response); 1123 } 1124 catch (final Exception e) 1125 { 1126 Debug.debugException(e); 1127 1128 if (e instanceof InterruptedException) 1129 { 1130 Thread.currentThread().interrupt(); 1131 } 1132 1133 throw new LDAPException(ResultCode.LOCAL_ERROR, 1134 ERR_EXCEPTION_HANDLING_RESPONSE.get( 1135 StaticUtils.getExceptionMessage(e)), e); 1136 } 1137 } 1138 1139 1140 1141 /** 1142 * {@inheritDoc} 1143 */ 1144 @Override() 1145 public int getLastMessageID() 1146 { 1147 return messageID; 1148 } 1149 1150 1151 1152 /** 1153 * {@inheritDoc} 1154 */ 1155 @Override() 1156 @NotNull() 1157 public OperationType getOperationType() 1158 { 1159 return OperationType.MODIFY_DN; 1160 } 1161 1162 1163 1164 /** 1165 * {@inheritDoc} 1166 */ 1167 @Override() 1168 @NotNull() 1169 public ModifyDNRequest duplicate() 1170 { 1171 return duplicate(getControls()); 1172 } 1173 1174 1175 1176 /** 1177 * {@inheritDoc} 1178 */ 1179 @Override() 1180 @NotNull() 1181 public ModifyDNRequest duplicate(@NotNull final Control[] controls) 1182 { 1183 final ModifyDNRequest r = new ModifyDNRequest(dn, newRDN, deleteOldRDN, 1184 newSuperiorDN, controls); 1185 1186 if (followReferralsInternal() != null) 1187 { 1188 r.setFollowReferrals(followReferralsInternal()); 1189 } 1190 1191 if (getReferralConnectorInternal() != null) 1192 { 1193 r.setReferralConnector(getReferralConnectorInternal()); 1194 } 1195 1196 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 1197 1198 return r; 1199 } 1200 1201 1202 1203 /** 1204 * {@inheritDoc} 1205 */ 1206 @Override() 1207 @NotNull() 1208 public LDIFModifyDNChangeRecord toLDIFChangeRecord() 1209 { 1210 return new LDIFModifyDNChangeRecord(this); 1211 } 1212 1213 1214 1215 /** 1216 * {@inheritDoc} 1217 */ 1218 @Override() 1219 @NotNull() 1220 public String[] toLDIF() 1221 { 1222 return toLDIFChangeRecord().toLDIF(); 1223 } 1224 1225 1226 1227 /** 1228 * {@inheritDoc} 1229 */ 1230 @Override() 1231 @NotNull() 1232 public String toLDIFString() 1233 { 1234 return toLDIFChangeRecord().toLDIFString(); 1235 } 1236 1237 1238 1239 /** 1240 * {@inheritDoc} 1241 */ 1242 @Override() 1243 public void toString(@NotNull final StringBuilder buffer) 1244 { 1245 buffer.append("ModifyDNRequest(dn='"); 1246 buffer.append(dn); 1247 buffer.append("', newRDN='"); 1248 buffer.append(newRDN); 1249 buffer.append("', deleteOldRDN="); 1250 buffer.append(deleteOldRDN); 1251 1252 if (newSuperiorDN != null) 1253 { 1254 buffer.append(", newSuperiorDN='"); 1255 buffer.append(newSuperiorDN); 1256 buffer.append('\''); 1257 } 1258 1259 final Control[] controls = getControls(); 1260 if (controls.length > 0) 1261 { 1262 buffer.append(", controls={"); 1263 for (int i=0; i < controls.length; i++) 1264 { 1265 if (i > 0) 1266 { 1267 buffer.append(", "); 1268 } 1269 1270 buffer.append(controls[i]); 1271 } 1272 buffer.append('}'); 1273 } 1274 1275 buffer.append(')'); 1276 } 1277 1278 1279 1280 /** 1281 * {@inheritDoc} 1282 */ 1283 @Override() 1284 public void toCode(@NotNull final List<String> lineList, 1285 @NotNull final String requestID, 1286 final int indentSpaces, final boolean includeProcessing) 1287 { 1288 // Create the request variable. 1289 final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(4); 1290 constructorArgs.add(ToCodeArgHelper.createString(dn, "Current DN")); 1291 constructorArgs.add(ToCodeArgHelper.createString(newRDN, "New RDN")); 1292 constructorArgs.add(ToCodeArgHelper.createBoolean(deleteOldRDN, 1293 "Delete Old RDN Value(s)")); 1294 1295 if (newSuperiorDN != null) 1296 { 1297 constructorArgs.add(ToCodeArgHelper.createString(newSuperiorDN, 1298 "New Superior Entry DN")); 1299 } 1300 1301 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ModifyDNRequest", 1302 requestID + "Request", "new ModifyDNRequest", constructorArgs); 1303 1304 1305 // If there are any controls, then add them to the request. 1306 for (final Control c : getControls()) 1307 { 1308 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1309 requestID + "Request.addControl", 1310 ToCodeArgHelper.createControl(c, null)); 1311 } 1312 1313 1314 // Add lines for processing the request and obtaining the result. 1315 if (includeProcessing) 1316 { 1317 // Generate a string with the appropriate indent. 1318 final StringBuilder buffer = new StringBuilder(); 1319 for (int i=0; i < indentSpaces; i++) 1320 { 1321 buffer.append(' '); 1322 } 1323 final String indent = buffer.toString(); 1324 1325 lineList.add(""); 1326 lineList.add(indent + "try"); 1327 lineList.add(indent + '{'); 1328 lineList.add(indent + " LDAPResult " + requestID + 1329 "Result = connection.modifyDN(" + requestID + "Request);"); 1330 lineList.add(indent + " // The modify DN was processed successfully."); 1331 lineList.add(indent + '}'); 1332 lineList.add(indent + "catch (LDAPException e)"); 1333 lineList.add(indent + '{'); 1334 lineList.add(indent + " // The modify DN failed. Maybe the following " + 1335 "will help explain why."); 1336 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 1337 lineList.add(indent + " String message = e.getMessage();"); 1338 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 1339 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 1340 lineList.add(indent + " Control[] responseControls = " + 1341 "e.getResponseControls();"); 1342 lineList.add(indent + '}'); 1343 } 1344 } 1345}