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