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