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