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.InternalUseOnly; 043import com.unboundid.util.Mutable; 044import com.unboundid.util.ThreadSafety; 045import com.unboundid.util.ThreadSafetyLevel; 046 047import static com.unboundid.ldap.sdk.LDAPMessages.*; 048import static com.unboundid.util.Debug.*; 049import static com.unboundid.util.StaticUtils.*; 050import static com.unboundid.util.Validator.*; 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<LDAPResponse>(); 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 = nanosToMillis(System.nanoTime() - requestTime); 875 if (connection.getConnectionOptions().abandonOnTimeout()) 876 { 877 connection.abandon(messageID); 878 } 879 880 throw new LDAPException(ResultCode.TIMEOUT, 881 ERR_MODIFY_DN_CLIENT_TIMEOUT.get(waitTime, messageID, dn, 882 connection.getHostPort())); 883 } 884 885 connection.getConnectionStatistics().incrementNumModifyDNResponses( 886 System.nanoTime() - requestTime); 887 if (response instanceof ConnectionClosedResponse) 888 { 889 // The connection was closed while waiting for the response. 890 if (allowRetry) 891 { 892 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 893 ResultCode.SERVER_DOWN); 894 if (retryResult != null) 895 { 896 return retryResult; 897 } 898 } 899 900 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 901 final String message = ccr.getMessage(); 902 if (message == null) 903 { 904 throw new LDAPException(ccr.getResultCode(), 905 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_DN_RESPONSE.get( 906 connection.getHostPort(), toString())); 907 } 908 else 909 { 910 throw new LDAPException(ccr.getResultCode(), 911 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_DN_RESPONSE_WITH_MESSAGE.get( 912 connection.getHostPort(), toString(), message)); 913 } 914 } 915 916 final LDAPResult result = (LDAPResult) response; 917 if ((result.getResultCode().equals(ResultCode.REFERRAL)) && 918 followReferrals(connection)) 919 { 920 if (depth >= connection.getConnectionOptions().getReferralHopLimit()) 921 { 922 return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED, 923 ERR_TOO_MANY_REFERRALS.get(), 924 result.getMatchedDN(), result.getReferralURLs(), 925 result.getResponseControls()); 926 } 927 928 return followReferral(result, connection, depth); 929 } 930 else 931 { 932 if (allowRetry) 933 { 934 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 935 result.getResultCode()); 936 if (retryResult != null) 937 { 938 return retryResult; 939 } 940 } 941 942 return result; 943 } 944 } 945 946 947 948 /** 949 * Attempts to re-establish the connection and retry processing this request 950 * on it. 951 * 952 * @param connection The connection to be re-established. 953 * @param depth The current referral depth for this request. It should 954 * always be one for the initial request, and should only 955 * be incremented when following referrals. 956 * @param resultCode The result code for the previous operation attempt. 957 * 958 * @return The result from re-trying the add, or {@code null} if it could not 959 * be re-tried. 960 */ 961 private LDAPResult reconnectAndRetry(final LDAPConnection connection, 962 final int depth, 963 final ResultCode resultCode) 964 { 965 try 966 { 967 // We will only want to retry for certain result codes that indicate a 968 // connection problem. 969 switch (resultCode.intValue()) 970 { 971 case ResultCode.SERVER_DOWN_INT_VALUE: 972 case ResultCode.DECODING_ERROR_INT_VALUE: 973 case ResultCode.CONNECT_ERROR_INT_VALUE: 974 connection.reconnect(); 975 return processSync(connection, depth, false); 976 } 977 } 978 catch (final Exception e) 979 { 980 debugException(e); 981 } 982 983 return null; 984 } 985 986 987 988 /** 989 * Attempts to follow a referral to perform a modify DN operation in the 990 * target server. 991 * 992 * @param referralResult The LDAP result object containing information about 993 * the referral to follow. 994 * @param connection The connection on which the referral was received. 995 * @param depth The number of referrals followed in the course of 996 * processing this request. 997 * 998 * @return The result of attempting to process the modify DN operation by 999 * following the referral. 1000 * 1001 * @throws LDAPException If a problem occurs while attempting to establish 1002 * the referral connection, sending the request, or 1003 * reading the result. 1004 */ 1005 private LDAPResult followReferral(final LDAPResult referralResult, 1006 final LDAPConnection connection, 1007 final int depth) 1008 throws LDAPException 1009 { 1010 for (final String urlString : referralResult.getReferralURLs()) 1011 { 1012 try 1013 { 1014 final LDAPURL referralURL = new LDAPURL(urlString); 1015 final String host = referralURL.getHost(); 1016 1017 if (host == null) 1018 { 1019 // We can't handle a referral in which there is no host. 1020 continue; 1021 } 1022 1023 final ModifyDNRequest modifyDNRequest; 1024 if (referralURL.baseDNProvided()) 1025 { 1026 modifyDNRequest = 1027 new ModifyDNRequest(referralURL.getBaseDN().toString(), 1028 newRDN, deleteOldRDN, newSuperiorDN, 1029 getControls()); 1030 } 1031 else 1032 { 1033 modifyDNRequest = this; 1034 } 1035 1036 final LDAPConnection referralConn = connection.getReferralConnector(). 1037 getReferralConnection(referralURL, connection); 1038 try 1039 { 1040 return modifyDNRequest.process(referralConn, depth+1); 1041 } 1042 finally 1043 { 1044 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null); 1045 referralConn.close(); 1046 } 1047 } 1048 catch (final LDAPException le) 1049 { 1050 debugException(le); 1051 } 1052 } 1053 1054 // If we've gotten here, then we could not follow any of the referral URLs, 1055 // so we'll just return the original referral result. 1056 return referralResult; 1057 } 1058 1059 1060 1061 /** 1062 * {@inheritDoc} 1063 */ 1064 @InternalUseOnly() 1065 @Override() 1066 public void responseReceived(final LDAPResponse response) 1067 throws LDAPException 1068 { 1069 try 1070 { 1071 responseQueue.put(response); 1072 } 1073 catch (final Exception e) 1074 { 1075 debugException(e); 1076 1077 if (e instanceof InterruptedException) 1078 { 1079 Thread.currentThread().interrupt(); 1080 } 1081 1082 throw new LDAPException(ResultCode.LOCAL_ERROR, 1083 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); 1084 } 1085 } 1086 1087 1088 1089 /** 1090 * {@inheritDoc} 1091 */ 1092 @Override() 1093 public int getLastMessageID() 1094 { 1095 return messageID; 1096 } 1097 1098 1099 1100 /** 1101 * {@inheritDoc} 1102 */ 1103 @Override() 1104 public OperationType getOperationType() 1105 { 1106 return OperationType.MODIFY_DN; 1107 } 1108 1109 1110 1111 /** 1112 * {@inheritDoc} 1113 */ 1114 @Override() 1115 public ModifyDNRequest duplicate() 1116 { 1117 return duplicate(getControls()); 1118 } 1119 1120 1121 1122 /** 1123 * {@inheritDoc} 1124 */ 1125 @Override() 1126 public ModifyDNRequest duplicate(final Control[] controls) 1127 { 1128 final ModifyDNRequest r = new ModifyDNRequest(dn, newRDN, deleteOldRDN, 1129 newSuperiorDN, controls); 1130 1131 if (followReferralsInternal() != null) 1132 { 1133 r.setFollowReferrals(followReferralsInternal()); 1134 } 1135 1136 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 1137 1138 return r; 1139 } 1140 1141 1142 1143 /** 1144 * {@inheritDoc} 1145 */ 1146 @Override() 1147 public LDIFModifyDNChangeRecord toLDIFChangeRecord() 1148 { 1149 return new LDIFModifyDNChangeRecord(this); 1150 } 1151 1152 1153 1154 /** 1155 * {@inheritDoc} 1156 */ 1157 @Override() 1158 public String[] toLDIF() 1159 { 1160 return toLDIFChangeRecord().toLDIF(); 1161 } 1162 1163 1164 1165 /** 1166 * {@inheritDoc} 1167 */ 1168 @Override() 1169 public String toLDIFString() 1170 { 1171 return toLDIFChangeRecord().toLDIFString(); 1172 } 1173 1174 1175 1176 /** 1177 * {@inheritDoc} 1178 */ 1179 @Override() 1180 public void toString(final StringBuilder buffer) 1181 { 1182 buffer.append("ModifyDNRequest(dn='"); 1183 buffer.append(dn); 1184 buffer.append("', newRDN='"); 1185 buffer.append(newRDN); 1186 buffer.append("', deleteOldRDN="); 1187 buffer.append(deleteOldRDN); 1188 1189 if (newSuperiorDN != null) 1190 { 1191 buffer.append(", newSuperiorDN='"); 1192 buffer.append(newSuperiorDN); 1193 buffer.append('\''); 1194 } 1195 1196 final Control[] controls = getControls(); 1197 if (controls.length > 0) 1198 { 1199 buffer.append(", controls={"); 1200 for (int i=0; i < controls.length; i++) 1201 { 1202 if (i > 0) 1203 { 1204 buffer.append(", "); 1205 } 1206 1207 buffer.append(controls[i]); 1208 } 1209 buffer.append('}'); 1210 } 1211 1212 buffer.append(')'); 1213 } 1214 1215 1216 1217 /** 1218 * {@inheritDoc} 1219 */ 1220 @Override() 1221 public void toCode(final List<String> lineList, final String requestID, 1222 final int indentSpaces, final boolean includeProcessing) 1223 { 1224 // Create the request variable. 1225 final ArrayList<ToCodeArgHelper> constructorArgs = 1226 new ArrayList<ToCodeArgHelper>(4); 1227 constructorArgs.add(ToCodeArgHelper.createString(dn, "Current DN")); 1228 constructorArgs.add(ToCodeArgHelper.createString(newRDN, "New RDN")); 1229 constructorArgs.add(ToCodeArgHelper.createBoolean(deleteOldRDN, 1230 "Delete Old RDN Value(s)")); 1231 1232 if (newSuperiorDN != null) 1233 { 1234 constructorArgs.add(ToCodeArgHelper.createString(newSuperiorDN, 1235 "New Superior Entry DN")); 1236 } 1237 1238 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ModifyDNRequest", 1239 requestID + "Request", "new ModifyDNRequest", constructorArgs); 1240 1241 1242 // If there are any controls, then add them to the request. 1243 for (final Control c : getControls()) 1244 { 1245 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1246 requestID + "Request.addControl", 1247 ToCodeArgHelper.createControl(c, null)); 1248 } 1249 1250 1251 // Add lines for processing the request and obtaining the result. 1252 if (includeProcessing) 1253 { 1254 // Generate a string with the appropriate indent. 1255 final StringBuilder buffer = new StringBuilder(); 1256 for (int i=0; i < indentSpaces; i++) 1257 { 1258 buffer.append(' '); 1259 } 1260 final String indent = buffer.toString(); 1261 1262 lineList.add(""); 1263 lineList.add(indent + "try"); 1264 lineList.add(indent + '{'); 1265 lineList.add(indent + " LDAPResult " + requestID + 1266 "Result = connection.modifyDN(" + requestID + "Request);"); 1267 lineList.add(indent + " // The modify DN was processed successfully."); 1268 lineList.add(indent + '}'); 1269 lineList.add(indent + "catch (LDAPException e)"); 1270 lineList.add(indent + '{'); 1271 lineList.add(indent + " // The modify DN failed. Maybe the following " + 1272 "will help explain why."); 1273 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 1274 lineList.add(indent + " String message = e.getMessage();"); 1275 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 1276 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 1277 lineList.add(indent + " Control[] responseControls = " + 1278 "e.getResponseControls();"); 1279 lineList.add(indent + '}'); 1280 } 1281 } 1282}