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