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