001/* 002 * Copyright 2007-2026 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2026 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-2026 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 return processSync(connection, depth); 683 } 684 685 final long requestTime = System.nanoTime(); 686 processAsync(connection, null); 687 688 try 689 { 690 // Wait for and process the response. 691 final LDAPResponse response; 692 try 693 { 694 final long responseTimeout = getResponseTimeoutMillis(connection); 695 if (responseTimeout > 0) 696 { 697 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS); 698 } 699 else 700 { 701 response = responseQueue.take(); 702 } 703 } 704 catch (final InterruptedException ie) 705 { 706 Debug.debugException(ie); 707 Thread.currentThread().interrupt(); 708 throw new LDAPException(ResultCode.LOCAL_ERROR, 709 ERR_MODIFY_INTERRUPTED.get(connection.getHostPort()), ie); 710 } 711 712 return handleResponse(connection, response, requestTime, depth); 713 } 714 finally 715 { 716 connection.deregisterResponseAcceptor(messageID); 717 } 718 } 719 720 721 722 /** 723 * Sends this modify request to the directory server over the provided 724 * connection and returns the message ID for the request. 725 * 726 * @param connection The connection to use to communicate with the 727 * directory server. 728 * @param resultListener The async result listener that is to be notified 729 * when the response is received. It may be 730 * {@code null} only if the result is to be processed 731 * by this class. 732 * 733 * @return The async request ID created for the operation, or {@code null} if 734 * the provided {@code resultListener} is {@code null} and the 735 * operation will not actually be processed asynchronously. 736 * 737 * @throws LDAPException If a problem occurs while sending the request. 738 */ 739 @Nullable() 740 AsyncRequestID processAsync(@NotNull final LDAPConnection connection, 741 @Nullable final AsyncResultListener resultListener) 742 throws LDAPException 743 { 744 // Create the LDAP message. 745 messageID = connection.nextMessageID(); 746 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 747 748 749 // If the provided async result listener is {@code null}, then we'll use 750 // this class as the message acceptor. Otherwise, create an async helper 751 // and use it as the message acceptor. 752 final AsyncRequestID asyncRequestID; 753 final long timeout = getResponseTimeoutMillis(connection); 754 if (resultListener == null) 755 { 756 asyncRequestID = null; 757 connection.registerResponseAcceptor(messageID, this); 758 } 759 else 760 { 761 final AsyncHelper helper = new AsyncHelper(connection, 762 OperationType.MODIFY, messageID, resultListener, 763 getIntermediateResponseListener()); 764 connection.registerResponseAcceptor(messageID, helper); 765 asyncRequestID = helper.getAsyncRequestID(); 766 767 if (timeout > 0L) 768 { 769 final Timer timer = connection.getTimer(); 770 final AsyncTimeoutTimerTask timerTask = 771 new AsyncTimeoutTimerTask(helper); 772 timer.schedule(timerTask, timeout); 773 asyncRequestID.setTimerTask(timerTask); 774 } 775 } 776 777 778 // Send the request to the server. 779 try 780 { 781 Debug.debugLDAPRequest(Level.INFO, this, messageID, connection); 782 783 final LDAPConnectionLogger logger = 784 connection.getConnectionOptions().getConnectionLogger(); 785 if (logger != null) 786 { 787 logger.logModifyRequest(connection, messageID, this); 788 } 789 790 connection.getConnectionStatistics().incrementNumModifyRequests(); 791 connection.sendMessage(message, timeout); 792 return asyncRequestID; 793 } 794 catch (final LDAPException le) 795 { 796 Debug.debugException(le); 797 798 connection.deregisterResponseAcceptor(messageID); 799 throw le; 800 } 801 } 802 803 804 805 /** 806 * Processes this modify operation in synchronous mode, in which the same 807 * thread will send the request and read the response. 808 * 809 * @param connection The connection to use to communicate with the directory 810 * server. 811 * @param depth The current referral depth for this request. It should 812 * always be one for the initial request, and should only 813 * be incremented when following referrals. 814 * 815 * @return An LDAP result object that provides information about the result 816 * of the modify processing. 817 * 818 * @throws LDAPException If a problem occurs while sending the request or 819 * reading the response. 820 */ 821 @NotNull() 822 private LDAPResult processSync(@NotNull final LDAPConnection connection, 823 final int depth) 824 throws LDAPException 825 { 826 // Create the LDAP message. 827 messageID = connection.nextMessageID(); 828 final LDAPMessage message = 829 new LDAPMessage(messageID, this, getControls()); 830 831 832 // Send the request to the server. 833 final long requestTime = System.nanoTime(); 834 Debug.debugLDAPRequest(Level.INFO, this, messageID, connection); 835 836 final LDAPConnectionLogger logger = 837 connection.getConnectionOptions().getConnectionLogger(); 838 if (logger != null) 839 { 840 logger.logModifyRequest(connection, messageID, this); 841 } 842 843 connection.getConnectionStatistics().incrementNumModifyRequests(); 844 connection.sendMessage(message, getResponseTimeoutMillis(connection)); 845 846 while (true) 847 { 848 final LDAPResponse response; 849 try 850 { 851 response = connection.readResponse(messageID); 852 } 853 catch (final LDAPException le) 854 { 855 Debug.debugException(le); 856 857 if ((le.getResultCode() == ResultCode.TIMEOUT) && 858 connection.getConnectionOptions().abandonOnTimeout()) 859 { 860 connection.abandon(messageID); 861 } 862 863 throw le; 864 } 865 866 if (response instanceof IntermediateResponse) 867 { 868 final IntermediateResponseListener listener = 869 getIntermediateResponseListener(); 870 if (listener != null) 871 { 872 listener.intermediateResponseReturned( 873 (IntermediateResponse) response); 874 } 875 } 876 else 877 { 878 return handleResponse(connection, response, requestTime, depth); 879 } 880 } 881 } 882 883 884 885 /** 886 * Performs the necessary processing for handling a response. 887 * 888 * @param connection The connection used to read the response. 889 * @param response The response to be processed. 890 * @param requestTime The time the request was sent to the server. 891 * @param depth The current referral depth for this request. It 892 * should always be one for the initial request, and 893 * should only be incremented when following referrals. 894 * 895 * @return The modify result. 896 * 897 * @throws LDAPException If a problem occurs. 898 */ 899 @NotNull() 900 private LDAPResult handleResponse(@NotNull final LDAPConnection connection, 901 @Nullable final LDAPResponse response, 902 final long requestTime, final int depth) 903 throws LDAPException 904 { 905 if (response == null) 906 { 907 final long waitTime = 908 StaticUtils.nanosToMillis(System.nanoTime() - requestTime); 909 if (connection.getConnectionOptions().abandonOnTimeout()) 910 { 911 connection.abandon(messageID); 912 } 913 914 throw new LDAPException(ResultCode.TIMEOUT, 915 ERR_MODIFY_CLIENT_TIMEOUT.get(waitTime, messageID, dn, 916 connection.getHostPort())); 917 } 918 919 connection.getConnectionStatistics().incrementNumModifyResponses( 920 System.nanoTime() - requestTime); 921 if (response instanceof ConnectionClosedResponse) 922 { 923 // The connection was closed while waiting for the response. 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 ReferralHelper.handleReferral(this, result, connection); 953 } 954 else 955 { 956 return result; 957 } 958 } 959 960 961 962 /** 963 * {@inheritDoc} 964 */ 965 @InternalUseOnly() 966 @Override() 967 public void responseReceived(@NotNull final LDAPResponse response) 968 throws LDAPException 969 { 970 try 971 { 972 responseQueue.put(response); 973 } 974 catch (final Exception e) 975 { 976 Debug.debugException(e); 977 978 if (e instanceof InterruptedException) 979 { 980 Thread.currentThread().interrupt(); 981 } 982 983 throw new LDAPException(ResultCode.LOCAL_ERROR, 984 ERR_EXCEPTION_HANDLING_RESPONSE.get( 985 StaticUtils.getExceptionMessage(e)), 986 e); 987 } 988 } 989 990 991 992 /** 993 * {@inheritDoc} 994 */ 995 @Override() 996 public int getLastMessageID() 997 { 998 return messageID; 999 } 1000 1001 1002 1003 /** 1004 * {@inheritDoc} 1005 */ 1006 @Override() 1007 @NotNull() 1008 public OperationType getOperationType() 1009 { 1010 return OperationType.MODIFY; 1011 } 1012 1013 1014 1015 /** 1016 * {@inheritDoc} 1017 */ 1018 @Override() 1019 @NotNull() 1020 public ModifyRequest duplicate() 1021 { 1022 return duplicate(getControls()); 1023 } 1024 1025 1026 1027 /** 1028 * {@inheritDoc} 1029 */ 1030 @Override() 1031 @NotNull() 1032 public ModifyRequest duplicate(@Nullable final Control[] controls) 1033 { 1034 final ModifyRequest r = new ModifyRequest(dn, 1035 new ArrayList<>(modifications), controls); 1036 1037 if (followReferralsInternal() != null) 1038 { 1039 r.setFollowReferrals(followReferralsInternal()); 1040 } 1041 1042 if (getReferralConnectorInternal() != null) 1043 { 1044 r.setReferralConnector(getReferralConnectorInternal()); 1045 } 1046 1047 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 1048 r.setIntermediateResponseListener(getIntermediateResponseListener()); 1049 r.setReferralDepth(getReferralDepth()); 1050 1051 return r; 1052 } 1053 1054 1055 1056 /** 1057 * {@inheritDoc} 1058 */ 1059 @Override() 1060 @NotNull() 1061 public LDIFModifyChangeRecord toLDIFChangeRecord() 1062 { 1063 return new LDIFModifyChangeRecord(this); 1064 } 1065 1066 1067 1068 /** 1069 * {@inheritDoc} 1070 */ 1071 @Override() 1072 @NotNull() 1073 public String[] toLDIF() 1074 { 1075 return toLDIFChangeRecord().toLDIF(); 1076 } 1077 1078 1079 1080 /** 1081 * {@inheritDoc} 1082 */ 1083 @Override() 1084 @NotNull() 1085 public String toLDIFString() 1086 { 1087 return toLDIFChangeRecord().toLDIFString(); 1088 } 1089 1090 1091 1092 /** 1093 * {@inheritDoc} 1094 */ 1095 @Override() 1096 public void toString(@NotNull final StringBuilder buffer) 1097 { 1098 buffer.append("ModifyRequest(dn='"); 1099 buffer.append(dn); 1100 buffer.append("', mods={"); 1101 for (int i=0; i < modifications.size(); i++) 1102 { 1103 final Modification m = modifications.get(i); 1104 1105 if (i > 0) 1106 { 1107 buffer.append(", "); 1108 } 1109 1110 switch (m.getModificationType().intValue()) 1111 { 1112 case 0: 1113 buffer.append("ADD "); 1114 break; 1115 1116 case 1: 1117 buffer.append("DELETE "); 1118 break; 1119 1120 case 2: 1121 buffer.append("REPLACE "); 1122 break; 1123 1124 case 3: 1125 buffer.append("INCREMENT "); 1126 break; 1127 } 1128 1129 buffer.append(m.getAttributeName()); 1130 } 1131 buffer.append('}'); 1132 1133 final Control[] controls = getControls(); 1134 if (controls.length > 0) 1135 { 1136 buffer.append(", controls={"); 1137 for (int i=0; i < controls.length; i++) 1138 { 1139 if (i > 0) 1140 { 1141 buffer.append(", "); 1142 } 1143 1144 buffer.append(controls[i]); 1145 } 1146 buffer.append('}'); 1147 } 1148 1149 buffer.append(')'); 1150 } 1151 1152 1153 1154 /** 1155 * {@inheritDoc} 1156 */ 1157 @Override() 1158 public void toCode(@NotNull final List<String> lineList, 1159 @NotNull final String requestID, 1160 final int indentSpaces, final boolean includeProcessing) 1161 { 1162 // Create the request variable. 1163 final ArrayList<ToCodeArgHelper> constructorArgs = 1164 new ArrayList<>(modifications.size() + 1); 1165 constructorArgs.add(ToCodeArgHelper.createString(dn, "Entry DN")); 1166 1167 boolean firstMod = true; 1168 for (final Modification m : modifications) 1169 { 1170 final String comment; 1171 if (firstMod) 1172 { 1173 firstMod = false; 1174 comment = "Modifications"; 1175 } 1176 else 1177 { 1178 comment = null; 1179 } 1180 1181 constructorArgs.add(ToCodeArgHelper.createModification(m, comment)); 1182 } 1183 1184 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ModifyRequest", 1185 requestID + "Request", "new ModifyRequest", constructorArgs); 1186 1187 1188 // If there are any controls, then add them to the request. 1189 for (final Control c : getControls()) 1190 { 1191 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1192 requestID + "Request.addControl", 1193 ToCodeArgHelper.createControl(c, null)); 1194 } 1195 1196 1197 // Add lines for processing the request and obtaining the result. 1198 if (includeProcessing) 1199 { 1200 // Generate a string with the appropriate indent. 1201 final StringBuilder buffer = new StringBuilder(); 1202 for (int i=0; i < indentSpaces; i++) 1203 { 1204 buffer.append(' '); 1205 } 1206 final String indent = buffer.toString(); 1207 1208 lineList.add(""); 1209 lineList.add(indent + "try"); 1210 lineList.add(indent + '{'); 1211 lineList.add(indent + " LDAPResult " + requestID + 1212 "Result = connection.modify(" + requestID + "Request);"); 1213 lineList.add(indent + " // The modify was processed successfully."); 1214 lineList.add(indent + '}'); 1215 lineList.add(indent + "catch (LDAPException e)"); 1216 lineList.add(indent + '{'); 1217 lineList.add(indent + " // The modify failed. Maybe the following " + 1218 "will help explain why."); 1219 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 1220 lineList.add(indent + " String message = e.getMessage();"); 1221 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 1222 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 1223 lineList.add(indent + " Control[] responseControls = " + 1224 "e.getResponseControls();"); 1225 lineList.add(indent + '}'); 1226 } 1227 } 1228}