001/* 002 * Copyright 2007-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2017 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 Thread.currentThread().interrupt(); 646 throw new LDAPException(ResultCode.LOCAL_ERROR, 647 ERR_MODDN_INTERRUPTED.get(connection.getHostPort()), ie); 648 } 649 650 return handleResponse(connection, response, requestTime, depth, false); 651 } 652 finally 653 { 654 connection.deregisterResponseAcceptor(messageID); 655 } 656 } 657 658 659 660 /** 661 * Sends this modify DN request to the directory server over the provided 662 * connection and returns the message ID for the request. 663 * 664 * @param connection The connection to use to communicate with the 665 * directory server. 666 * @param resultListener The async result listener that is to be notified 667 * when the response is received. It may be 668 * {@code null} only if the result is to be processed 669 * by this class. 670 * 671 * @return The async request ID created for the operation, or {@code null} if 672 * the provided {@code resultListener} is {@code null} and the 673 * operation will not actually be processed asynchronously. 674 * 675 * @throws LDAPException If a problem occurs while sending the request. 676 */ 677 AsyncRequestID processAsync(final LDAPConnection connection, 678 final AsyncResultListener resultListener) 679 throws LDAPException 680 { 681 // Create the LDAP message. 682 messageID = connection.nextMessageID(); 683 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 684 685 686 // If the provided async result listener is {@code null}, then we'll use 687 // this class as the message acceptor. Otherwise, create an async helper 688 // and use it as the message acceptor. 689 final AsyncRequestID asyncRequestID; 690 if (resultListener == null) 691 { 692 asyncRequestID = null; 693 connection.registerResponseAcceptor(messageID, this); 694 } 695 else 696 { 697 final AsyncHelper helper = new AsyncHelper(connection, 698 OperationType.MODIFY_DN, messageID, resultListener, 699 getIntermediateResponseListener()); 700 connection.registerResponseAcceptor(messageID, helper); 701 asyncRequestID = helper.getAsyncRequestID(); 702 703 final long timeout = getResponseTimeoutMillis(connection); 704 if (timeout > 0L) 705 { 706 final Timer timer = connection.getTimer(); 707 final AsyncTimeoutTimerTask timerTask = 708 new AsyncTimeoutTimerTask(helper); 709 timer.schedule(timerTask, timeout); 710 asyncRequestID.setTimerTask(timerTask); 711 } 712 } 713 714 715 // Send the request to the server. 716 try 717 { 718 debugLDAPRequest(this); 719 connection.getConnectionStatistics().incrementNumModifyDNRequests(); 720 connection.sendMessage(message); 721 return asyncRequestID; 722 } 723 catch (LDAPException le) 724 { 725 debugException(le); 726 727 connection.deregisterResponseAcceptor(messageID); 728 throw le; 729 } 730 } 731 732 733 734 /** 735 * Processes this modify DN operation in synchronous mode, in which the same 736 * thread will send the request and read the response. 737 * 738 * @param connection The connection to use to communicate with the directory 739 * server. 740 * @param depth The current referral depth for this request. It should 741 * always be one for the initial request, and should only 742 * be incremented when following referrals. 743 * @param allowRetry Indicates whether the request may be re-tried on a 744 * re-established connection if the initial attempt fails 745 * in a way that indicates the connection is no longer 746 * valid and autoReconnect is true. 747 * 748 * @return An LDAP result object that provides information about the result 749 * of the modify DN processing. 750 * 751 * @throws LDAPException If a problem occurs while sending the request or 752 * reading the response. 753 */ 754 private LDAPResult processSync(final LDAPConnection connection, 755 final int depth, 756 final boolean allowRetry) 757 throws LDAPException 758 { 759 // Create the LDAP message. 760 messageID = connection.nextMessageID(); 761 final LDAPMessage message = 762 new LDAPMessage(messageID, this, getControls()); 763 764 765 // Set the appropriate timeout on the socket. 766 try 767 { 768 connection.getConnectionInternals(true).getSocket().setSoTimeout( 769 (int) getResponseTimeoutMillis(connection)); 770 } 771 catch (Exception e) 772 { 773 debugException(e); 774 } 775 776 777 // Send the request to the server. 778 final long requestTime = System.nanoTime(); 779 debugLDAPRequest(this); 780 connection.getConnectionStatistics().incrementNumModifyDNRequests(); 781 try 782 { 783 connection.sendMessage(message); 784 } 785 catch (final LDAPException le) 786 { 787 debugException(le); 788 789 if (allowRetry) 790 { 791 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 792 le.getResultCode()); 793 if (retryResult != null) 794 { 795 return retryResult; 796 } 797 } 798 799 throw le; 800 } 801 802 while (true) 803 { 804 final LDAPResponse response; 805 try 806 { 807 response = connection.readResponse(messageID); 808 } 809 catch (final LDAPException le) 810 { 811 debugException(le); 812 813 if ((le.getResultCode() == ResultCode.TIMEOUT) && 814 connection.getConnectionOptions().abandonOnTimeout()) 815 { 816 connection.abandon(messageID); 817 } 818 819 if (allowRetry) 820 { 821 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 822 le.getResultCode()); 823 if (retryResult != null) 824 { 825 return retryResult; 826 } 827 } 828 829 throw le; 830 } 831 832 if (response instanceof IntermediateResponse) 833 { 834 final IntermediateResponseListener listener = 835 getIntermediateResponseListener(); 836 if (listener != null) 837 { 838 listener.intermediateResponseReturned( 839 (IntermediateResponse) response); 840 } 841 } 842 else 843 { 844 return handleResponse(connection, response, requestTime, depth, 845 allowRetry); 846 } 847 } 848 } 849 850 851 852 /** 853 * Performs the necessary processing for handling a response. 854 * 855 * @param connection The connection used to read the response. 856 * @param response The response to be processed. 857 * @param requestTime The time the request was sent to the server. 858 * @param depth The current referral depth for this request. It 859 * should always be one for the initial request, and 860 * should only be incremented when following referrals. 861 * @param allowRetry Indicates whether the request may be re-tried on a 862 * re-established connection if the initial attempt fails 863 * in a way that indicates the connection is no longer 864 * valid and autoReconnect is true. 865 * 866 * @return The modify DN result. 867 * 868 * @throws LDAPException If a problem occurs. 869 */ 870 private LDAPResult handleResponse(final LDAPConnection connection, 871 final LDAPResponse response, 872 final long requestTime, final int depth, 873 final boolean allowRetry) 874 throws LDAPException 875 { 876 if (response == null) 877 { 878 final long waitTime = nanosToMillis(System.nanoTime() - requestTime); 879 if (connection.getConnectionOptions().abandonOnTimeout()) 880 { 881 connection.abandon(messageID); 882 } 883 884 throw new LDAPException(ResultCode.TIMEOUT, 885 ERR_MODIFY_DN_CLIENT_TIMEOUT.get(waitTime, messageID, dn, 886 connection.getHostPort())); 887 } 888 889 connection.getConnectionStatistics().incrementNumModifyDNResponses( 890 System.nanoTime() - requestTime); 891 if (response instanceof ConnectionClosedResponse) 892 { 893 // The connection was closed while waiting for the response. 894 if (allowRetry) 895 { 896 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 897 ResultCode.SERVER_DOWN); 898 if (retryResult != null) 899 { 900 return retryResult; 901 } 902 } 903 904 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 905 final String message = ccr.getMessage(); 906 if (message == null) 907 { 908 throw new LDAPException(ccr.getResultCode(), 909 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_DN_RESPONSE.get( 910 connection.getHostPort(), toString())); 911 } 912 else 913 { 914 throw new LDAPException(ccr.getResultCode(), 915 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_DN_RESPONSE_WITH_MESSAGE.get( 916 connection.getHostPort(), toString(), message)); 917 } 918 } 919 920 final LDAPResult result = (LDAPResult) response; 921 if ((result.getResultCode().equals(ResultCode.REFERRAL)) && 922 followReferrals(connection)) 923 { 924 if (depth >= connection.getConnectionOptions().getReferralHopLimit()) 925 { 926 return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED, 927 ERR_TOO_MANY_REFERRALS.get(), 928 result.getMatchedDN(), result.getReferralURLs(), 929 result.getResponseControls()); 930 } 931 932 return followReferral(result, connection, depth); 933 } 934 else 935 { 936 if (allowRetry) 937 { 938 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 939 result.getResultCode()); 940 if (retryResult != null) 941 { 942 return retryResult; 943 } 944 } 945 946 return result; 947 } 948 } 949 950 951 952 /** 953 * Attempts to re-establish the connection and retry processing this request 954 * on it. 955 * 956 * @param connection The connection to be re-established. 957 * @param depth The current referral depth for this request. It should 958 * always be one for the initial request, and should only 959 * be incremented when following referrals. 960 * @param resultCode The result code for the previous operation attempt. 961 * 962 * @return The result from re-trying the add, or {@code null} if it could not 963 * be re-tried. 964 */ 965 private LDAPResult reconnectAndRetry(final LDAPConnection connection, 966 final int depth, 967 final ResultCode resultCode) 968 { 969 try 970 { 971 // We will only want to retry for certain result codes that indicate a 972 // connection problem. 973 switch (resultCode.intValue()) 974 { 975 case ResultCode.SERVER_DOWN_INT_VALUE: 976 case ResultCode.DECODING_ERROR_INT_VALUE: 977 case ResultCode.CONNECT_ERROR_INT_VALUE: 978 connection.reconnect(); 979 return processSync(connection, depth, false); 980 } 981 } 982 catch (final Exception e) 983 { 984 debugException(e); 985 } 986 987 return null; 988 } 989 990 991 992 /** 993 * Attempts to follow a referral to perform a modify DN operation in the 994 * target server. 995 * 996 * @param referralResult The LDAP result object containing information about 997 * the referral to follow. 998 * @param connection The connection on which the referral was received. 999 * @param depth The number of referrals followed in the course of 1000 * processing this request. 1001 * 1002 * @return The result of attempting to process the modify DN operation by 1003 * following the referral. 1004 * 1005 * @throws LDAPException If a problem occurs while attempting to establish 1006 * the referral connection, sending the request, or 1007 * reading the result. 1008 */ 1009 private LDAPResult followReferral(final LDAPResult referralResult, 1010 final LDAPConnection connection, 1011 final int depth) 1012 throws LDAPException 1013 { 1014 for (final String urlString : referralResult.getReferralURLs()) 1015 { 1016 try 1017 { 1018 final LDAPURL referralURL = new LDAPURL(urlString); 1019 final String host = referralURL.getHost(); 1020 1021 if (host == null) 1022 { 1023 // We can't handle a referral in which there is no host. 1024 continue; 1025 } 1026 1027 final ModifyDNRequest modifyDNRequest; 1028 if (referralURL.baseDNProvided()) 1029 { 1030 modifyDNRequest = 1031 new ModifyDNRequest(referralURL.getBaseDN().toString(), 1032 newRDN, deleteOldRDN, newSuperiorDN, 1033 getControls()); 1034 } 1035 else 1036 { 1037 modifyDNRequest = this; 1038 } 1039 1040 final LDAPConnection referralConn = connection.getReferralConnector(). 1041 getReferralConnection(referralURL, connection); 1042 try 1043 { 1044 return modifyDNRequest.process(referralConn, depth+1); 1045 } 1046 finally 1047 { 1048 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null); 1049 referralConn.close(); 1050 } 1051 } 1052 catch (LDAPException le) 1053 { 1054 debugException(le); 1055 } 1056 } 1057 1058 // If we've gotten here, then we could not follow any of the referral URLs, 1059 // so we'll just return the original referral result. 1060 return referralResult; 1061 } 1062 1063 1064 1065 /** 1066 * {@inheritDoc} 1067 */ 1068 @InternalUseOnly() 1069 public void responseReceived(final LDAPResponse response) 1070 throws LDAPException 1071 { 1072 try 1073 { 1074 responseQueue.put(response); 1075 } 1076 catch (Exception e) 1077 { 1078 debugException(e); 1079 1080 if (e instanceof InterruptedException) 1081 { 1082 Thread.currentThread().interrupt(); 1083 } 1084 1085 throw new LDAPException(ResultCode.LOCAL_ERROR, 1086 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); 1087 } 1088 } 1089 1090 1091 1092 /** 1093 * {@inheritDoc} 1094 */ 1095 @Override() 1096 public int getLastMessageID() 1097 { 1098 return messageID; 1099 } 1100 1101 1102 1103 /** 1104 * {@inheritDoc} 1105 */ 1106 @Override() 1107 public OperationType getOperationType() 1108 { 1109 return OperationType.MODIFY_DN; 1110 } 1111 1112 1113 1114 /** 1115 * {@inheritDoc} 1116 */ 1117 public ModifyDNRequest duplicate() 1118 { 1119 return duplicate(getControls()); 1120 } 1121 1122 1123 1124 /** 1125 * {@inheritDoc} 1126 */ 1127 public ModifyDNRequest duplicate(final Control[] controls) 1128 { 1129 final ModifyDNRequest r = new ModifyDNRequest(dn, newRDN, deleteOldRDN, 1130 newSuperiorDN, controls); 1131 1132 if (followReferralsInternal() != null) 1133 { 1134 r.setFollowReferrals(followReferralsInternal()); 1135 } 1136 1137 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 1138 1139 return r; 1140 } 1141 1142 1143 1144 /** 1145 * {@inheritDoc} 1146 */ 1147 public LDIFModifyDNChangeRecord toLDIFChangeRecord() 1148 { 1149 return new LDIFModifyDNChangeRecord(this); 1150 } 1151 1152 1153 1154 /** 1155 * {@inheritDoc} 1156 */ 1157 public String[] toLDIF() 1158 { 1159 return toLDIFChangeRecord().toLDIF(); 1160 } 1161 1162 1163 1164 /** 1165 * {@inheritDoc} 1166 */ 1167 public String toLDIFString() 1168 { 1169 return toLDIFChangeRecord().toLDIFString(); 1170 } 1171 1172 1173 1174 /** 1175 * {@inheritDoc} 1176 */ 1177 @Override() 1178 public void toString(final StringBuilder buffer) 1179 { 1180 buffer.append("ModifyDNRequest(dn='"); 1181 buffer.append(dn); 1182 buffer.append("', newRDN='"); 1183 buffer.append(newRDN); 1184 buffer.append("', deleteOldRDN="); 1185 buffer.append(deleteOldRDN); 1186 1187 if (newSuperiorDN != null) 1188 { 1189 buffer.append(", newSuperiorDN='"); 1190 buffer.append(newSuperiorDN); 1191 buffer.append('\''); 1192 } 1193 1194 final Control[] controls = getControls(); 1195 if (controls.length > 0) 1196 { 1197 buffer.append(", controls={"); 1198 for (int i=0; i < controls.length; i++) 1199 { 1200 if (i > 0) 1201 { 1202 buffer.append(", "); 1203 } 1204 1205 buffer.append(controls[i]); 1206 } 1207 buffer.append('}'); 1208 } 1209 1210 buffer.append(')'); 1211 } 1212 1213 1214 1215 /** 1216 * {@inheritDoc} 1217 */ 1218 public void toCode(final List<String> lineList, final String requestID, 1219 final int indentSpaces, final boolean includeProcessing) 1220 { 1221 // Create the request variable. 1222 final ArrayList<ToCodeArgHelper> constructorArgs = 1223 new ArrayList<ToCodeArgHelper>(4); 1224 constructorArgs.add(ToCodeArgHelper.createString(dn, "Current DN")); 1225 constructorArgs.add(ToCodeArgHelper.createString(newRDN, "New RDN")); 1226 constructorArgs.add(ToCodeArgHelper.createBoolean(deleteOldRDN, 1227 "Delete Old RDN Value(s)")); 1228 1229 if (newSuperiorDN != null) 1230 { 1231 constructorArgs.add(ToCodeArgHelper.createString(newSuperiorDN, 1232 "New Superior Entry DN")); 1233 } 1234 1235 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ModifyDNRequest", 1236 requestID + "Request", "new ModifyDNRequest", constructorArgs); 1237 1238 1239 // If there are any controls, then add them to the request. 1240 for (final Control c : getControls()) 1241 { 1242 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1243 requestID + "Request.addControl", 1244 ToCodeArgHelper.createControl(c, null)); 1245 } 1246 1247 1248 // Add lines for processing the request and obtaining the result. 1249 if (includeProcessing) 1250 { 1251 // Generate a string with the appropriate indent. 1252 final StringBuilder buffer = new StringBuilder(); 1253 for (int i=0; i < indentSpaces; i++) 1254 { 1255 buffer.append(' '); 1256 } 1257 final String indent = buffer.toString(); 1258 1259 lineList.add(""); 1260 lineList.add(indent + "try"); 1261 lineList.add(indent + '{'); 1262 lineList.add(indent + " LDAPResult " + requestID + 1263 "Result = connection.modifyDN(" + requestID + "Request);"); 1264 lineList.add(indent + " // The modify DN was processed successfully."); 1265 lineList.add(indent + '}'); 1266 lineList.add(indent + "catch (LDAPException e)"); 1267 lineList.add(indent + '{'); 1268 lineList.add(indent + " // The modify DN failed. Maybe the following " + 1269 "will help explain why."); 1270 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 1271 lineList.add(indent + " String message = e.getMessage();"); 1272 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 1273 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 1274 lineList.add(indent + " Control[] responseControls = " + 1275 "e.getResponseControls();"); 1276 lineList.add(indent + '}'); 1277 } 1278 } 1279}