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