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