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