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