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