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