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