001/* 002 * Copyright 2007-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2017 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 @SuppressWarnings("deprecation") 644 final boolean autoReconnect = 645 connection.getConnectionOptions().autoReconnect(); 646 return processSync(connection, depth, autoReconnect); 647 } 648 649 final long requestTime = System.nanoTime(); 650 processAsync(connection, null); 651 652 try 653 { 654 // Wait for and process the response. 655 final LDAPResponse response; 656 try 657 { 658 final long responseTimeout = getResponseTimeoutMillis(connection); 659 if (responseTimeout > 0) 660 { 661 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS); 662 } 663 else 664 { 665 response = responseQueue.take(); 666 } 667 } 668 catch (InterruptedException ie) 669 { 670 debugException(ie); 671 Thread.currentThread().interrupt(); 672 throw new LDAPException(ResultCode.LOCAL_ERROR, 673 ERR_MODIFY_INTERRUPTED.get(connection.getHostPort()), ie); 674 } 675 676 return handleResponse(connection, response, requestTime, depth, false); 677 } 678 finally 679 { 680 connection.deregisterResponseAcceptor(messageID); 681 } 682 } 683 684 685 686 /** 687 * Sends this modify request to the directory server over the provided 688 * connection and returns the message ID for the request. 689 * 690 * @param connection The connection to use to communicate with the 691 * directory server. 692 * @param resultListener The async result listener that is to be notified 693 * when the response is received. It may be 694 * {@code null} only if the result is to be processed 695 * by this class. 696 * 697 * @return The async request ID created for the operation, or {@code null} if 698 * the provided {@code resultListener} is {@code null} and the 699 * operation will not actually be processed asynchronously. 700 * 701 * @throws LDAPException If a problem occurs while sending the request. 702 */ 703 AsyncRequestID processAsync(final LDAPConnection connection, 704 final AsyncResultListener resultListener) 705 throws LDAPException 706 { 707 // Create the LDAP message. 708 messageID = connection.nextMessageID(); 709 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 710 711 712 // If the provided async result listener is {@code null}, then we'll use 713 // this class as the message acceptor. Otherwise, create an async helper 714 // and use it as the message acceptor. 715 final AsyncRequestID asyncRequestID; 716 if (resultListener == null) 717 { 718 asyncRequestID = null; 719 connection.registerResponseAcceptor(messageID, this); 720 } 721 else 722 { 723 final AsyncHelper helper = new AsyncHelper(connection, 724 OperationType.MODIFY, messageID, resultListener, 725 getIntermediateResponseListener()); 726 connection.registerResponseAcceptor(messageID, helper); 727 asyncRequestID = helper.getAsyncRequestID(); 728 729 final long timeout = getResponseTimeoutMillis(connection); 730 if (timeout > 0L) 731 { 732 final Timer timer = connection.getTimer(); 733 final AsyncTimeoutTimerTask timerTask = 734 new AsyncTimeoutTimerTask(helper); 735 timer.schedule(timerTask, timeout); 736 asyncRequestID.setTimerTask(timerTask); 737 } 738 } 739 740 741 // Send the request to the server. 742 try 743 { 744 debugLDAPRequest(this); 745 connection.getConnectionStatistics().incrementNumModifyRequests(); 746 connection.sendMessage(message); 747 return asyncRequestID; 748 } 749 catch (LDAPException le) 750 { 751 debugException(le); 752 753 connection.deregisterResponseAcceptor(messageID); 754 throw le; 755 } 756 } 757 758 759 760 /** 761 * Processes this modify operation in synchronous mode, in which the same 762 * thread will send the request and read the response. 763 * 764 * @param connection The connection to use to communicate with the directory 765 * server. 766 * @param depth The current referral depth for this request. It should 767 * always be one for the initial request, and should only 768 * be incremented when following referrals. 769 * @param allowRetry Indicates whether the request may be re-tried on a 770 * re-established connection if the initial attempt fails 771 * in a way that indicates the connection is no longer 772 * valid and autoReconnect is true. 773 * 774 * @return An LDAP result object that provides information about the result 775 * of the modify processing. 776 * 777 * @throws LDAPException If a problem occurs while sending the request or 778 * reading the response. 779 */ 780 private LDAPResult processSync(final LDAPConnection connection, 781 final int depth, final boolean allowRetry) 782 throws LDAPException 783 { 784 // Create the LDAP message. 785 messageID = connection.nextMessageID(); 786 final LDAPMessage message = 787 new LDAPMessage(messageID, this, getControls()); 788 789 790 // Set the appropriate timeout on the socket. 791 try 792 { 793 connection.getConnectionInternals(true).getSocket().setSoTimeout( 794 (int) getResponseTimeoutMillis(connection)); 795 } 796 catch (Exception e) 797 { 798 debugException(e); 799 } 800 801 802 // Send the request to the server. 803 final long requestTime = System.nanoTime(); 804 debugLDAPRequest(this); 805 connection.getConnectionStatistics().incrementNumModifyRequests(); 806 try 807 { 808 connection.sendMessage(message); 809 } 810 catch (final LDAPException le) 811 { 812 debugException(le); 813 814 if (allowRetry) 815 { 816 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 817 le.getResultCode()); 818 if (retryResult != null) 819 { 820 return retryResult; 821 } 822 } 823 824 throw le; 825 } 826 827 while (true) 828 { 829 final LDAPResponse response; 830 try 831 { 832 response = connection.readResponse(messageID); 833 } 834 catch (final LDAPException le) 835 { 836 debugException(le); 837 838 if ((le.getResultCode() == ResultCode.TIMEOUT) && 839 connection.getConnectionOptions().abandonOnTimeout()) 840 { 841 connection.abandon(messageID); 842 } 843 844 if (allowRetry) 845 { 846 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 847 le.getResultCode()); 848 if (retryResult != null) 849 { 850 return retryResult; 851 } 852 } 853 854 throw le; 855 } 856 857 if (response instanceof IntermediateResponse) 858 { 859 final IntermediateResponseListener listener = 860 getIntermediateResponseListener(); 861 if (listener != null) 862 { 863 listener.intermediateResponseReturned( 864 (IntermediateResponse) response); 865 } 866 } 867 else 868 { 869 return handleResponse(connection, response, requestTime, depth, 870 allowRetry); 871 } 872 } 873 } 874 875 876 877 /** 878 * Performs the necessary processing for handling a response. 879 * 880 * @param connection The connection used to read the response. 881 * @param response The response to be processed. 882 * @param requestTime The time the request was sent to the server. 883 * @param depth The current referral depth for this request. It 884 * should always be one for the initial request, and 885 * should only be incremented when following referrals. 886 * @param allowRetry Indicates whether the request may be re-tried on a 887 * re-established connection if the initial attempt fails 888 * in a way that indicates the connection is no longer 889 * valid and autoReconnect is true. 890 * 891 * @return The modify result. 892 * 893 * @throws LDAPException If a problem occurs. 894 */ 895 private LDAPResult handleResponse(final LDAPConnection connection, 896 final LDAPResponse response, 897 final long requestTime, final int depth, 898 final boolean allowRetry) 899 throws LDAPException 900 { 901 if (response == null) 902 { 903 final long waitTime = nanosToMillis(System.nanoTime() - requestTime); 904 if (connection.getConnectionOptions().abandonOnTimeout()) 905 { 906 connection.abandon(messageID); 907 } 908 909 throw new LDAPException(ResultCode.TIMEOUT, 910 ERR_MODIFY_CLIENT_TIMEOUT.get(waitTime, messageID, dn, 911 connection.getHostPort())); 912 } 913 914 connection.getConnectionStatistics().incrementNumModifyResponses( 915 System.nanoTime() - requestTime); 916 if (response instanceof ConnectionClosedResponse) 917 { 918 // The connection was closed while waiting for the response. 919 if (allowRetry) 920 { 921 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 922 ResultCode.SERVER_DOWN); 923 if (retryResult != null) 924 { 925 return retryResult; 926 } 927 } 928 929 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 930 final String message = ccr.getMessage(); 931 if (message == null) 932 { 933 throw new LDAPException(ccr.getResultCode(), 934 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_RESPONSE.get( 935 connection.getHostPort(), toString())); 936 } 937 else 938 { 939 throw new LDAPException(ccr.getResultCode(), 940 ERR_CONN_CLOSED_WAITING_FOR_MODIFY_RESPONSE_WITH_MESSAGE.get( 941 connection.getHostPort(), toString(), message)); 942 } 943 } 944 945 final LDAPResult result = (LDAPResult) response; 946 if ((result.getResultCode().equals(ResultCode.REFERRAL)) && 947 followReferrals(connection)) 948 { 949 if (depth >= connection.getConnectionOptions().getReferralHopLimit()) 950 { 951 return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED, 952 ERR_TOO_MANY_REFERRALS.get(), 953 result.getMatchedDN(), result.getReferralURLs(), 954 result.getResponseControls()); 955 } 956 957 return followReferral(result, connection, depth); 958 } 959 else 960 { 961 if (allowRetry) 962 { 963 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 964 result.getResultCode()); 965 if (retryResult != null) 966 { 967 return retryResult; 968 } 969 } 970 971 return result; 972 } 973 } 974 975 976 977 /** 978 * Attempts to re-establish the connection and retry processing this request 979 * on it. 980 * 981 * @param connection The connection to be re-established. 982 * @param depth The current referral depth for this request. It should 983 * always be one for the initial request, and should only 984 * be incremented when following referrals. 985 * @param resultCode The result code for the previous operation attempt. 986 * 987 * @return The result from re-trying the add, or {@code null} if it could not 988 * be re-tried. 989 */ 990 private LDAPResult reconnectAndRetry(final LDAPConnection connection, 991 final int depth, 992 final ResultCode resultCode) 993 { 994 try 995 { 996 // We will only want to retry for certain result codes that indicate a 997 // connection problem. 998 switch (resultCode.intValue()) 999 { 1000 case ResultCode.SERVER_DOWN_INT_VALUE: 1001 case ResultCode.DECODING_ERROR_INT_VALUE: 1002 case ResultCode.CONNECT_ERROR_INT_VALUE: 1003 connection.reconnect(); 1004 return processSync(connection, depth, false); 1005 } 1006 } 1007 catch (final Exception e) 1008 { 1009 debugException(e); 1010 } 1011 1012 return null; 1013 } 1014 1015 1016 1017 /** 1018 * Attempts to follow a referral to perform a modify operation in the target 1019 * server. 1020 * 1021 * @param referralResult The LDAP result object containing information about 1022 * the referral to follow. 1023 * @param connection The connection on which the referral was received. 1024 * @param depth The number of referrals followed in the course of 1025 * processing this request. 1026 * 1027 * @return The result of attempting to process the modify operation by 1028 * following the referral. 1029 * 1030 * @throws LDAPException If a problem occurs while attempting to establish 1031 * the referral connection, sending the request, or 1032 * reading the result. 1033 */ 1034 private LDAPResult followReferral(final LDAPResult referralResult, 1035 final LDAPConnection connection, 1036 final int depth) 1037 throws LDAPException 1038 { 1039 for (final String urlString : referralResult.getReferralURLs()) 1040 { 1041 try 1042 { 1043 final LDAPURL referralURL = new LDAPURL(urlString); 1044 final String host = referralURL.getHost(); 1045 1046 if (host == null) 1047 { 1048 // We can't handle a referral in which there is no host. 1049 continue; 1050 } 1051 1052 final ModifyRequest modifyRequest; 1053 if (referralURL.baseDNProvided()) 1054 { 1055 modifyRequest = new ModifyRequest(referralURL.getBaseDN(), 1056 modifications, getControls()); 1057 } 1058 else 1059 { 1060 modifyRequest = this; 1061 } 1062 1063 final LDAPConnection referralConn = connection.getReferralConnector(). 1064 getReferralConnection(referralURL, connection); 1065 try 1066 { 1067 return modifyRequest.process(referralConn, depth+1); 1068 } 1069 finally 1070 { 1071 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null); 1072 referralConn.close(); 1073 } 1074 } 1075 catch (LDAPException le) 1076 { 1077 debugException(le); 1078 } 1079 } 1080 1081 // If we've gotten here, then we could not follow any of the referral URLs, 1082 // so we'll just return the original referral result. 1083 return referralResult; 1084 } 1085 1086 1087 1088 /** 1089 * {@inheritDoc} 1090 */ 1091 @InternalUseOnly() 1092 public void responseReceived(final LDAPResponse response) 1093 throws LDAPException 1094 { 1095 try 1096 { 1097 responseQueue.put(response); 1098 } 1099 catch (Exception e) 1100 { 1101 debugException(e); 1102 1103 if (e instanceof InterruptedException) 1104 { 1105 Thread.currentThread().interrupt(); 1106 } 1107 1108 throw new LDAPException(ResultCode.LOCAL_ERROR, 1109 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); 1110 } 1111 } 1112 1113 1114 1115 /** 1116 * {@inheritDoc} 1117 */ 1118 @Override() 1119 public int getLastMessageID() 1120 { 1121 return messageID; 1122 } 1123 1124 1125 1126 /** 1127 * {@inheritDoc} 1128 */ 1129 @Override() 1130 public OperationType getOperationType() 1131 { 1132 return OperationType.MODIFY; 1133 } 1134 1135 1136 1137 /** 1138 * {@inheritDoc} 1139 */ 1140 public ModifyRequest duplicate() 1141 { 1142 return duplicate(getControls()); 1143 } 1144 1145 1146 1147 /** 1148 * {@inheritDoc} 1149 */ 1150 public ModifyRequest duplicate(final Control[] controls) 1151 { 1152 final ModifyRequest r = new ModifyRequest(dn, 1153 new ArrayList<Modification>(modifications), controls); 1154 1155 if (followReferralsInternal() != null) 1156 { 1157 r.setFollowReferrals(followReferralsInternal()); 1158 } 1159 1160 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 1161 1162 return r; 1163 } 1164 1165 1166 1167 /** 1168 * {@inheritDoc} 1169 */ 1170 public LDIFModifyChangeRecord toLDIFChangeRecord() 1171 { 1172 return new LDIFModifyChangeRecord(this); 1173 } 1174 1175 1176 1177 /** 1178 * {@inheritDoc} 1179 */ 1180 public String[] toLDIF() 1181 { 1182 return toLDIFChangeRecord().toLDIF(); 1183 } 1184 1185 1186 1187 /** 1188 * {@inheritDoc} 1189 */ 1190 public String toLDIFString() 1191 { 1192 return toLDIFChangeRecord().toLDIFString(); 1193 } 1194 1195 1196 1197 /** 1198 * {@inheritDoc} 1199 */ 1200 @Override() 1201 public void toString(final StringBuilder buffer) 1202 { 1203 buffer.append("ModifyRequest(dn='"); 1204 buffer.append(dn); 1205 buffer.append("', mods={"); 1206 for (int i=0; i < modifications.size(); i++) 1207 { 1208 final Modification m = modifications.get(i); 1209 1210 if (i > 0) 1211 { 1212 buffer.append(", "); 1213 } 1214 1215 switch (m.getModificationType().intValue()) 1216 { 1217 case 0: 1218 buffer.append("ADD "); 1219 break; 1220 1221 case 1: 1222 buffer.append("DELETE "); 1223 break; 1224 1225 case 2: 1226 buffer.append("REPLACE "); 1227 break; 1228 1229 case 3: 1230 buffer.append("INCREMENT "); 1231 break; 1232 } 1233 1234 buffer.append(m.getAttributeName()); 1235 } 1236 buffer.append('}'); 1237 1238 final Control[] controls = getControls(); 1239 if (controls.length > 0) 1240 { 1241 buffer.append(", controls={"); 1242 for (int i=0; i < controls.length; i++) 1243 { 1244 if (i > 0) 1245 { 1246 buffer.append(", "); 1247 } 1248 1249 buffer.append(controls[i]); 1250 } 1251 buffer.append('}'); 1252 } 1253 1254 buffer.append(')'); 1255 } 1256 1257 1258 1259 /** 1260 * {@inheritDoc} 1261 */ 1262 public void toCode(final List<String> lineList, final String requestID, 1263 final int indentSpaces, final boolean includeProcessing) 1264 { 1265 // Create the request variable. 1266 final ArrayList<ToCodeArgHelper> constructorArgs = 1267 new ArrayList<ToCodeArgHelper>(modifications.size() + 1); 1268 constructorArgs.add(ToCodeArgHelper.createString(dn, "Entry DN")); 1269 1270 boolean firstMod = true; 1271 for (final Modification m : modifications) 1272 { 1273 final String comment; 1274 if (firstMod) 1275 { 1276 firstMod = false; 1277 comment = "Modifications"; 1278 } 1279 else 1280 { 1281 comment = null; 1282 } 1283 1284 constructorArgs.add(ToCodeArgHelper.createModification(m, comment)); 1285 } 1286 1287 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ModifyRequest", 1288 requestID + "Request", "new ModifyRequest", constructorArgs); 1289 1290 1291 // If there are any controls, then add them to the request. 1292 for (final Control c : getControls()) 1293 { 1294 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1295 requestID + "Request.addControl", 1296 ToCodeArgHelper.createControl(c, null)); 1297 } 1298 1299 1300 // Add lines for processing the request and obtaining the result. 1301 if (includeProcessing) 1302 { 1303 // Generate a string with the appropriate indent. 1304 final StringBuilder buffer = new StringBuilder(); 1305 for (int i=0; i < indentSpaces; i++) 1306 { 1307 buffer.append(' '); 1308 } 1309 final String indent = buffer.toString(); 1310 1311 lineList.add(""); 1312 lineList.add(indent + "try"); 1313 lineList.add(indent + '{'); 1314 lineList.add(indent + " LDAPResult " + requestID + 1315 "Result = connection.modify(" + requestID + "Request);"); 1316 lineList.add(indent + " // The modify was processed successfully."); 1317 lineList.add(indent + '}'); 1318 lineList.add(indent + "catch (LDAPException e)"); 1319 lineList.add(indent + '{'); 1320 lineList.add(indent + " // The modify failed. Maybe the following " + 1321 "will help explain why."); 1322 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 1323 lineList.add(indent + " String message = e.getMessage();"); 1324 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 1325 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 1326 lineList.add(indent + " Control[] responseControls = " + 1327 "e.getResponseControls();"); 1328 lineList.add(indent + '}'); 1329 } 1330 } 1331}