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