001/* 002 * Copyright 2007-2017 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2017 Ping Identity Corporation 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 @Override() 389 public String getDN() 390 { 391 return dn; 392 } 393 394 395 396 /** 397 * Specifies the current DN of the entry to move/rename. 398 * 399 * @param dn The current DN of the entry to move/rename. It must not be 400 * {@code null}. 401 */ 402 public void setDN(final String dn) 403 { 404 ensureNotNull(dn); 405 406 this.dn = dn; 407 } 408 409 410 411 /** 412 * Specifies the current DN of the entry to move/rename. 413 * 414 * @param dn The current DN of the entry to move/rename. It must not be 415 * {@code null}. 416 */ 417 public void setDN(final DN dn) 418 { 419 ensureNotNull(dn); 420 421 this.dn = dn.toString(); 422 } 423 424 425 426 /** 427 * {@inheritDoc} 428 */ 429 @Override() 430 public String getNewRDN() 431 { 432 return newRDN; 433 } 434 435 436 437 /** 438 * Specifies the new RDN for the entry. 439 * 440 * @param newRDN The new RDN for the entry. It must not be {@code null}. 441 */ 442 public void setNewRDN(final String newRDN) 443 { 444 ensureNotNull(newRDN); 445 446 this.newRDN = newRDN; 447 } 448 449 450 451 /** 452 * Specifies the new RDN for the entry. 453 * 454 * @param newRDN The new RDN for the entry. It must not be {@code null}. 455 */ 456 public void setNewRDN(final RDN newRDN) 457 { 458 ensureNotNull(newRDN); 459 460 this.newRDN = newRDN.toString(); 461 } 462 463 464 465 /** 466 * {@inheritDoc} 467 */ 468 @Override() 469 public boolean deleteOldRDN() 470 { 471 return deleteOldRDN; 472 } 473 474 475 476 /** 477 * Specifies whether the current RDN value should be removed from the entry. 478 * 479 * @param deleteOldRDN Specifies whether the current RDN value should be 480 * removed from the entry. 481 */ 482 public void setDeleteOldRDN(final boolean deleteOldRDN) 483 { 484 this.deleteOldRDN = deleteOldRDN; 485 } 486 487 488 489 /** 490 * {@inheritDoc} 491 */ 492 @Override() 493 public String getNewSuperiorDN() 494 { 495 return newSuperiorDN; 496 } 497 498 499 500 /** 501 * Specifies the new superior DN for the entry. 502 * 503 * @param newSuperiorDN The new superior DN for the entry. It may be 504 * {@code null} if the entry is not to be removed below 505 * a new parent. 506 */ 507 public void setNewSuperiorDN(final String newSuperiorDN) 508 { 509 this.newSuperiorDN = newSuperiorDN; 510 } 511 512 513 514 /** 515 * Specifies the new superior DN for the entry. 516 * 517 * @param newSuperiorDN The new superior DN for the entry. It may be 518 * {@code null} if the entry is not to be removed below 519 * a new parent. 520 */ 521 public void setNewSuperiorDN(final DN newSuperiorDN) 522 { 523 if (newSuperiorDN == null) 524 { 525 this.newSuperiorDN = null; 526 } 527 else 528 { 529 this.newSuperiorDN = newSuperiorDN.toString(); 530 } 531 } 532 533 534 535 /** 536 * {@inheritDoc} 537 */ 538 @Override() 539 public byte getProtocolOpType() 540 { 541 return LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST; 542 } 543 544 545 546 /** 547 * {@inheritDoc} 548 */ 549 @Override() 550 public void writeTo(final ASN1Buffer writer) 551 { 552 final ASN1BufferSequence requestSequence = 553 writer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST); 554 writer.addOctetString(dn); 555 writer.addOctetString(newRDN); 556 writer.addBoolean(deleteOldRDN); 557 558 if (newSuperiorDN != null) 559 { 560 writer.addOctetString(NEW_SUPERIOR_TYPE, newSuperiorDN); 561 } 562 requestSequence.end(); 563 } 564 565 566 567 /** 568 * Encodes the modify DN request protocol op to an ASN.1 element. 569 * 570 * @return The ASN.1 element with the encoded modify DN request protocol op. 571 */ 572 @Override() 573 public ASN1Element encodeProtocolOp() 574 { 575 final ASN1Element[] protocolOpElements; 576 if (newSuperiorDN == null) 577 { 578 protocolOpElements = new ASN1Element[] 579 { 580 new ASN1OctetString(dn), 581 new ASN1OctetString(newRDN), 582 new ASN1Boolean(deleteOldRDN) 583 }; 584 } 585 else 586 { 587 protocolOpElements = new ASN1Element[] 588 { 589 new ASN1OctetString(dn), 590 new ASN1OctetString(newRDN), 591 new ASN1Boolean(deleteOldRDN), 592 new ASN1OctetString(NEW_SUPERIOR_TYPE, newSuperiorDN) 593 }; 594 } 595 596 return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST, 597 protocolOpElements); 598 } 599 600 601 602 /** 603 * Sends this modify DN request to the directory server over the provided 604 * connection and returns the associated response. 605 * 606 * @param connection The connection to use to communicate with the directory 607 * server. 608 * @param depth The current referral depth for this request. It should 609 * always be one for the initial request, and should only 610 * be incremented when following referrals. 611 * 612 * @return An LDAP result object that provides information about the result 613 * of the modify DN processing. 614 * 615 * @throws LDAPException If a problem occurs while sending the request or 616 * reading the response. 617 */ 618 @Override() 619 protected LDAPResult process(final LDAPConnection connection, final int depth) 620 throws LDAPException 621 { 622 if (connection.synchronousMode()) 623 { 624 @SuppressWarnings("deprecation") 625 final boolean autoReconnect = 626 connection.getConnectionOptions().autoReconnect(); 627 return processSync(connection, depth, autoReconnect); 628 } 629 630 final long requestTime = System.nanoTime(); 631 processAsync(connection, null); 632 633 try 634 { 635 // Wait for and process the response. 636 final LDAPResponse response; 637 try 638 { 639 final long responseTimeout = getResponseTimeoutMillis(connection); 640 if (responseTimeout > 0) 641 { 642 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS); 643 } 644 else 645 { 646 response = responseQueue.take(); 647 } 648 } 649 catch (final InterruptedException ie) 650 { 651 debugException(ie); 652 Thread.currentThread().interrupt(); 653 throw new LDAPException(ResultCode.LOCAL_ERROR, 654 ERR_MODDN_INTERRUPTED.get(connection.getHostPort()), ie); 655 } 656 657 return handleResponse(connection, response, requestTime, depth, false); 658 } 659 finally 660 { 661 connection.deregisterResponseAcceptor(messageID); 662 } 663 } 664 665 666 667 /** 668 * Sends this modify DN request to the directory server over the provided 669 * connection and returns the message ID for the request. 670 * 671 * @param connection The connection to use to communicate with the 672 * directory server. 673 * @param resultListener The async result listener that is to be notified 674 * when the response is received. It may be 675 * {@code null} only if the result is to be processed 676 * by this class. 677 * 678 * @return The async request ID created for the operation, or {@code null} if 679 * the provided {@code resultListener} is {@code null} and the 680 * operation will not actually be processed asynchronously. 681 * 682 * @throws LDAPException If a problem occurs while sending the request. 683 */ 684 AsyncRequestID processAsync(final LDAPConnection connection, 685 final AsyncResultListener resultListener) 686 throws LDAPException 687 { 688 // Create the LDAP message. 689 messageID = connection.nextMessageID(); 690 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 691 692 693 // If the provided async result listener is {@code null}, then we'll use 694 // this class as the message acceptor. Otherwise, create an async helper 695 // and use it as the message acceptor. 696 final AsyncRequestID asyncRequestID; 697 if (resultListener == null) 698 { 699 asyncRequestID = null; 700 connection.registerResponseAcceptor(messageID, this); 701 } 702 else 703 { 704 final AsyncHelper helper = new AsyncHelper(connection, 705 OperationType.MODIFY_DN, messageID, resultListener, 706 getIntermediateResponseListener()); 707 connection.registerResponseAcceptor(messageID, helper); 708 asyncRequestID = helper.getAsyncRequestID(); 709 710 final long timeout = getResponseTimeoutMillis(connection); 711 if (timeout > 0L) 712 { 713 final Timer timer = connection.getTimer(); 714 final AsyncTimeoutTimerTask timerTask = 715 new AsyncTimeoutTimerTask(helper); 716 timer.schedule(timerTask, timeout); 717 asyncRequestID.setTimerTask(timerTask); 718 } 719 } 720 721 722 // Send the request to the server. 723 try 724 { 725 debugLDAPRequest(this); 726 connection.getConnectionStatistics().incrementNumModifyDNRequests(); 727 connection.sendMessage(message); 728 return asyncRequestID; 729 } 730 catch (final LDAPException le) 731 { 732 debugException(le); 733 734 connection.deregisterResponseAcceptor(messageID); 735 throw le; 736 } 737 } 738 739 740 741 /** 742 * Processes this modify DN operation in synchronous mode, in which the same 743 * thread will send the request and read the response. 744 * 745 * @param connection The connection to use to communicate with the directory 746 * server. 747 * @param depth The current referral depth for this request. It should 748 * always be one for the initial request, and should only 749 * be incremented when following referrals. 750 * @param allowRetry Indicates whether the request may be re-tried on a 751 * re-established connection if the initial attempt fails 752 * in a way that indicates the connection is no longer 753 * valid and autoReconnect is true. 754 * 755 * @return An LDAP result object that provides information about the result 756 * of the modify DN processing. 757 * 758 * @throws LDAPException If a problem occurs while sending the request or 759 * reading the response. 760 */ 761 private LDAPResult processSync(final LDAPConnection connection, 762 final int depth, 763 final boolean allowRetry) 764 throws LDAPException 765 { 766 // Create the LDAP message. 767 messageID = connection.nextMessageID(); 768 final LDAPMessage message = 769 new LDAPMessage(messageID, this, getControls()); 770 771 772 // Set the appropriate timeout on the socket. 773 try 774 { 775 connection.getConnectionInternals(true).getSocket().setSoTimeout( 776 (int) getResponseTimeoutMillis(connection)); 777 } 778 catch (final Exception e) 779 { 780 debugException(e); 781 } 782 783 784 // Send the request to the server. 785 final long requestTime = System.nanoTime(); 786 debugLDAPRequest(this); 787 connection.getConnectionStatistics().incrementNumModifyDNRequests(); 788 try 789 { 790 connection.sendMessage(message); 791 } 792 catch (final LDAPException le) 793 { 794 debugException(le); 795 796 if (allowRetry) 797 { 798 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 799 le.getResultCode()); 800 if (retryResult != null) 801 { 802 return retryResult; 803 } 804 } 805 806 throw le; 807 } 808 809 while (true) 810 { 811 final LDAPResponse response; 812 try 813 { 814 response = connection.readResponse(messageID); 815 } 816 catch (final LDAPException le) 817 { 818 debugException(le); 819 820 if ((le.getResultCode() == ResultCode.TIMEOUT) && 821 connection.getConnectionOptions().abandonOnTimeout()) 822 { 823 connection.abandon(messageID); 824 } 825 826 if (allowRetry) 827 { 828 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 829 le.getResultCode()); 830 if (retryResult != null) 831 { 832 return retryResult; 833 } 834 } 835 836 throw le; 837 } 838 839 if (response instanceof IntermediateResponse) 840 { 841 final IntermediateResponseListener listener = 842 getIntermediateResponseListener(); 843 if (listener != null) 844 { 845 listener.intermediateResponseReturned( 846 (IntermediateResponse) response); 847 } 848 } 849 else 850 { 851 return handleResponse(connection, response, requestTime, depth, 852 allowRetry); 853 } 854 } 855 } 856 857 858 859 /** 860 * Performs the necessary processing for handling a response. 861 * 862 * @param connection The connection used to read the response. 863 * @param response The response to be processed. 864 * @param requestTime The time the request was sent to the server. 865 * @param depth The current referral depth for this request. It 866 * should always be one for the initial request, and 867 * should only be incremented when following referrals. 868 * @param allowRetry Indicates whether the request may be re-tried on a 869 * re-established connection if the initial attempt fails 870 * in a way that indicates the connection is no longer 871 * valid and autoReconnect is true. 872 * 873 * @return The modify DN result. 874 * 875 * @throws LDAPException If a problem occurs. 876 */ 877 private LDAPResult handleResponse(final LDAPConnection connection, 878 final LDAPResponse response, 879 final long requestTime, final int depth, 880 final boolean allowRetry) 881 throws LDAPException 882 { 883 if (response == null) 884 { 885 final long waitTime = nanosToMillis(System.nanoTime() - requestTime); 886 if (connection.getConnectionOptions().abandonOnTimeout()) 887 { 888 connection.abandon(messageID); 889 } 890 891 throw new LDAPException(ResultCode.TIMEOUT, 892 ERR_MODIFY_DN_CLIENT_TIMEOUT.get(waitTime, messageID, dn, 893 connection.getHostPort())); 894 } 895 896 connection.getConnectionStatistics().incrementNumModifyDNResponses( 897 System.nanoTime() - requestTime); 898 if (response instanceof ConnectionClosedResponse) 899 { 900 // The connection was closed while waiting for the response. 901 if (allowRetry) 902 { 903 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 904 ResultCode.SERVER_DOWN); 905 if (retryResult != null) 906 { 907 return retryResult; 908 } 909 } 910 911 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 912 final String message = ccr.getMessage(); 913 if (message == null) 914 { 915 throw new LDAPException(ccr.getResultCode(), 916 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_DN_RESPONSE.get( 917 connection.getHostPort(), toString())); 918 } 919 else 920 { 921 throw new LDAPException(ccr.getResultCode(), 922 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_DN_RESPONSE_WITH_MESSAGE.get( 923 connection.getHostPort(), toString(), message)); 924 } 925 } 926 927 final LDAPResult result = (LDAPResult) response; 928 if ((result.getResultCode().equals(ResultCode.REFERRAL)) && 929 followReferrals(connection)) 930 { 931 if (depth >= connection.getConnectionOptions().getReferralHopLimit()) 932 { 933 return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED, 934 ERR_TOO_MANY_REFERRALS.get(), 935 result.getMatchedDN(), result.getReferralURLs(), 936 result.getResponseControls()); 937 } 938 939 return followReferral(result, connection, depth); 940 } 941 else 942 { 943 if (allowRetry) 944 { 945 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 946 result.getResultCode()); 947 if (retryResult != null) 948 { 949 return retryResult; 950 } 951 } 952 953 return result; 954 } 955 } 956 957 958 959 /** 960 * Attempts to re-establish the connection and retry processing this request 961 * on it. 962 * 963 * @param connection The connection to be re-established. 964 * @param depth The current referral depth for this request. It should 965 * always be one for the initial request, and should only 966 * be incremented when following referrals. 967 * @param resultCode The result code for the previous operation attempt. 968 * 969 * @return The result from re-trying the add, or {@code null} if it could not 970 * be re-tried. 971 */ 972 private LDAPResult reconnectAndRetry(final LDAPConnection connection, 973 final int depth, 974 final ResultCode resultCode) 975 { 976 try 977 { 978 // We will only want to retry for certain result codes that indicate a 979 // connection problem. 980 switch (resultCode.intValue()) 981 { 982 case ResultCode.SERVER_DOWN_INT_VALUE: 983 case ResultCode.DECODING_ERROR_INT_VALUE: 984 case ResultCode.CONNECT_ERROR_INT_VALUE: 985 connection.reconnect(); 986 return processSync(connection, depth, false); 987 } 988 } 989 catch (final Exception e) 990 { 991 debugException(e); 992 } 993 994 return null; 995 } 996 997 998 999 /** 1000 * Attempts to follow a referral to perform a modify DN operation in the 1001 * target server. 1002 * 1003 * @param referralResult The LDAP result object containing information about 1004 * the referral to follow. 1005 * @param connection The connection on which the referral was received. 1006 * @param depth The number of referrals followed in the course of 1007 * processing this request. 1008 * 1009 * @return The result of attempting to process the modify DN operation by 1010 * following the referral. 1011 * 1012 * @throws LDAPException If a problem occurs while attempting to establish 1013 * the referral connection, sending the request, or 1014 * reading the result. 1015 */ 1016 private LDAPResult followReferral(final LDAPResult referralResult, 1017 final LDAPConnection connection, 1018 final int depth) 1019 throws LDAPException 1020 { 1021 for (final String urlString : referralResult.getReferralURLs()) 1022 { 1023 try 1024 { 1025 final LDAPURL referralURL = new LDAPURL(urlString); 1026 final String host = referralURL.getHost(); 1027 1028 if (host == null) 1029 { 1030 // We can't handle a referral in which there is no host. 1031 continue; 1032 } 1033 1034 final ModifyDNRequest modifyDNRequest; 1035 if (referralURL.baseDNProvided()) 1036 { 1037 modifyDNRequest = 1038 new ModifyDNRequest(referralURL.getBaseDN().toString(), 1039 newRDN, deleteOldRDN, newSuperiorDN, 1040 getControls()); 1041 } 1042 else 1043 { 1044 modifyDNRequest = this; 1045 } 1046 1047 final LDAPConnection referralConn = connection.getReferralConnector(). 1048 getReferralConnection(referralURL, connection); 1049 try 1050 { 1051 return modifyDNRequest.process(referralConn, depth+1); 1052 } 1053 finally 1054 { 1055 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null); 1056 referralConn.close(); 1057 } 1058 } 1059 catch (final LDAPException le) 1060 { 1061 debugException(le); 1062 } 1063 } 1064 1065 // If we've gotten here, then we could not follow any of the referral URLs, 1066 // so we'll just return the original referral result. 1067 return referralResult; 1068 } 1069 1070 1071 1072 /** 1073 * {@inheritDoc} 1074 */ 1075 @InternalUseOnly() 1076 @Override() 1077 public void responseReceived(final LDAPResponse response) 1078 throws LDAPException 1079 { 1080 try 1081 { 1082 responseQueue.put(response); 1083 } 1084 catch (final Exception e) 1085 { 1086 debugException(e); 1087 1088 if (e instanceof InterruptedException) 1089 { 1090 Thread.currentThread().interrupt(); 1091 } 1092 1093 throw new LDAPException(ResultCode.LOCAL_ERROR, 1094 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); 1095 } 1096 } 1097 1098 1099 1100 /** 1101 * {@inheritDoc} 1102 */ 1103 @Override() 1104 public int getLastMessageID() 1105 { 1106 return messageID; 1107 } 1108 1109 1110 1111 /** 1112 * {@inheritDoc} 1113 */ 1114 @Override() 1115 public OperationType getOperationType() 1116 { 1117 return OperationType.MODIFY_DN; 1118 } 1119 1120 1121 1122 /** 1123 * {@inheritDoc} 1124 */ 1125 @Override() 1126 public ModifyDNRequest duplicate() 1127 { 1128 return duplicate(getControls()); 1129 } 1130 1131 1132 1133 /** 1134 * {@inheritDoc} 1135 */ 1136 @Override() 1137 public ModifyDNRequest duplicate(final Control[] controls) 1138 { 1139 final ModifyDNRequest r = new ModifyDNRequest(dn, newRDN, deleteOldRDN, 1140 newSuperiorDN, controls); 1141 1142 if (followReferralsInternal() != null) 1143 { 1144 r.setFollowReferrals(followReferralsInternal()); 1145 } 1146 1147 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 1148 1149 return r; 1150 } 1151 1152 1153 1154 /** 1155 * {@inheritDoc} 1156 */ 1157 @Override() 1158 public LDIFModifyDNChangeRecord toLDIFChangeRecord() 1159 { 1160 return new LDIFModifyDNChangeRecord(this); 1161 } 1162 1163 1164 1165 /** 1166 * {@inheritDoc} 1167 */ 1168 @Override() 1169 public String[] toLDIF() 1170 { 1171 return toLDIFChangeRecord().toLDIF(); 1172 } 1173 1174 1175 1176 /** 1177 * {@inheritDoc} 1178 */ 1179 @Override() 1180 public String toLDIFString() 1181 { 1182 return toLDIFChangeRecord().toLDIFString(); 1183 } 1184 1185 1186 1187 /** 1188 * {@inheritDoc} 1189 */ 1190 @Override() 1191 public void toString(final StringBuilder buffer) 1192 { 1193 buffer.append("ModifyDNRequest(dn='"); 1194 buffer.append(dn); 1195 buffer.append("', newRDN='"); 1196 buffer.append(newRDN); 1197 buffer.append("', deleteOldRDN="); 1198 buffer.append(deleteOldRDN); 1199 1200 if (newSuperiorDN != null) 1201 { 1202 buffer.append(", newSuperiorDN='"); 1203 buffer.append(newSuperiorDN); 1204 buffer.append('\''); 1205 } 1206 1207 final Control[] controls = getControls(); 1208 if (controls.length > 0) 1209 { 1210 buffer.append(", controls={"); 1211 for (int i=0; i < controls.length; i++) 1212 { 1213 if (i > 0) 1214 { 1215 buffer.append(", "); 1216 } 1217 1218 buffer.append(controls[i]); 1219 } 1220 buffer.append('}'); 1221 } 1222 1223 buffer.append(')'); 1224 } 1225 1226 1227 1228 /** 1229 * {@inheritDoc} 1230 */ 1231 @Override() 1232 public void toCode(final List<String> lineList, final String requestID, 1233 final int indentSpaces, final boolean includeProcessing) 1234 { 1235 // Create the request variable. 1236 final ArrayList<ToCodeArgHelper> constructorArgs = 1237 new ArrayList<ToCodeArgHelper>(4); 1238 constructorArgs.add(ToCodeArgHelper.createString(dn, "Current DN")); 1239 constructorArgs.add(ToCodeArgHelper.createString(newRDN, "New RDN")); 1240 constructorArgs.add(ToCodeArgHelper.createBoolean(deleteOldRDN, 1241 "Delete Old RDN Value(s)")); 1242 1243 if (newSuperiorDN != null) 1244 { 1245 constructorArgs.add(ToCodeArgHelper.createString(newSuperiorDN, 1246 "New Superior Entry DN")); 1247 } 1248 1249 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ModifyDNRequest", 1250 requestID + "Request", "new ModifyDNRequest", constructorArgs); 1251 1252 1253 // If there are any controls, then add them to the request. 1254 for (final Control c : getControls()) 1255 { 1256 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1257 requestID + "Request.addControl", 1258 ToCodeArgHelper.createControl(c, null)); 1259 } 1260 1261 1262 // Add lines for processing the request and obtaining the result. 1263 if (includeProcessing) 1264 { 1265 // Generate a string with the appropriate indent. 1266 final StringBuilder buffer = new StringBuilder(); 1267 for (int i=0; i < indentSpaces; i++) 1268 { 1269 buffer.append(' '); 1270 } 1271 final String indent = buffer.toString(); 1272 1273 lineList.add(""); 1274 lineList.add(indent + "try"); 1275 lineList.add(indent + '{'); 1276 lineList.add(indent + " LDAPResult " + requestID + 1277 "Result = connection.modifyDN(" + requestID + "Request);"); 1278 lineList.add(indent + " // The modify DN was processed successfully."); 1279 lineList.add(indent + '}'); 1280 lineList.add(indent + "catch (LDAPException e)"); 1281 lineList.add(indent + '{'); 1282 lineList.add(indent + " // The modify DN failed. Maybe the following " + 1283 "will help explain why."); 1284 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 1285 lineList.add(indent + " String message = e.getMessage();"); 1286 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 1287 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 1288 lineList.add(indent + " Control[] responseControls = " + 1289 "e.getResponseControls();"); 1290 lineList.add(indent + '}'); 1291 } 1292 } 1293}