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