001 /* 002 * Copyright 2007-2015 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005 /* 006 * Copyright (C) 2008-2015 UnboundID Corp. 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 */ 021 package com.unboundid.ldap.sdk; 022 023 024 025 import java.util.ArrayList; 026 import java.util.Arrays; 027 import java.util.Collections; 028 import java.util.List; 029 import java.util.Timer; 030 import java.util.concurrent.LinkedBlockingQueue; 031 import java.util.concurrent.TimeUnit; 032 033 import com.unboundid.asn1.ASN1Buffer; 034 import com.unboundid.asn1.ASN1BufferSequence; 035 import com.unboundid.asn1.ASN1Element; 036 import com.unboundid.asn1.ASN1OctetString; 037 import com.unboundid.asn1.ASN1Sequence; 038 import com.unboundid.ldap.protocol.LDAPMessage; 039 import com.unboundid.ldap.protocol.LDAPResponse; 040 import com.unboundid.ldap.protocol.ProtocolOp; 041 import com.unboundid.ldif.LDIFChangeRecord; 042 import com.unboundid.ldif.LDIFException; 043 import com.unboundid.ldif.LDIFModifyChangeRecord; 044 import com.unboundid.ldif.LDIFReader; 045 import com.unboundid.util.InternalUseOnly; 046 047 import static com.unboundid.ldap.sdk.LDAPMessages.*; 048 import static com.unboundid.util.Debug.*; 049 import static com.unboundid.util.StaticUtils.*; 050 import static com.unboundid.util.Validator.*; 051 052 053 054 /** 055 * This class implements the processing necessary to perform an LDAPv3 modify 056 * operation, which can be used to update an entry in the directory server. A 057 * modify request contains the DN of the entry to modify, as well as one or more 058 * changes to apply to that entry. See the {@code Modification} class for more 059 * information about the types of modifications that may be processed. 060 * <BR><BR> 061 * A modify request can be created with a DN and set of modifications, but it 062 * can also be as a list of the lines that comprise the LDIF representation of 063 * the modification as described in 064 * <A HREF="http://www.ietf.org/rfc/rfc2849.txt">RFC 2849</A>. For example, the 065 * following code demonstrates creating a modify request from the LDIF 066 * representation of the modification: 067 * <PRE> 068 * ModifyRequest modifyRequest = new ModifyRequest( 069 * "dn: dc=example,dc=com", 070 * "changetype: modify", 071 * "replace: description", 072 * "description: This is the new description."); 073 * </PRE> 074 * <BR><BR> 075 * {@code ModifyRequest} objects are mutable and therefore can be altered and 076 * re-used for multiple requests. Note, however, that {@code ModifyRequest} 077 * objects are not threadsafe and therefore a single {@code ModifyRequest} 078 * object instance should not be used to process multiple requests at the same 079 * time. 080 */ 081 public final class ModifyRequest 082 extends UpdatableLDAPRequest 083 implements ReadOnlyModifyRequest, ResponseAcceptor, ProtocolOp 084 { 085 /** 086 * The serial version UID for this serializable class. 087 */ 088 private static final long serialVersionUID = -4747622844001634758L; 089 090 091 092 // The queue that will be used to receive response messages from the server. 093 private final LinkedBlockingQueue<LDAPResponse> responseQueue = 094 new LinkedBlockingQueue<LDAPResponse>(); 095 096 // The set of modifications to perform. 097 private final ArrayList<Modification> modifications; 098 099 // The message ID from the last LDAP message sent from this request. 100 private int messageID = -1; 101 102 // The DN of the entry to modify. 103 private String dn; 104 105 106 107 /** 108 * Creates a new modify request with the provided information. 109 * 110 * @param dn The DN of the entry to modify. It must not be {@code null}. 111 * @param mod The modification to apply to the entry. It must not be 112 * {@code null}. 113 */ 114 public ModifyRequest(final String dn, final Modification mod) 115 { 116 super(null); 117 118 ensureNotNull(dn, mod); 119 120 this.dn = dn; 121 122 modifications = new ArrayList<Modification>(1); 123 modifications.add(mod); 124 } 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 mods The set of modifications to apply to the entry. It must not 133 * be {@code null} or empty. 134 */ 135 public ModifyRequest(final String dn, final Modification... mods) 136 { 137 super(null); 138 139 ensureNotNull(dn, mods); 140 ensureFalse(mods.length == 0, 141 "ModifyRequest.mods must not be empty."); 142 143 this.dn = dn; 144 145 modifications = new ArrayList<Modification>(mods.length); 146 modifications.addAll(Arrays.asList(mods)); 147 } 148 149 150 151 /** 152 * Creates a new modify request with the provided information. 153 * 154 * @param dn The DN of the entry to modify. It must not be {@code null}. 155 * @param mods The set of modifications to apply to the entry. It must not 156 * be {@code null} or empty. 157 */ 158 public ModifyRequest(final String dn, final List<Modification> mods) 159 { 160 super(null); 161 162 ensureNotNull(dn, mods); 163 ensureFalse(mods.isEmpty(), 164 "ModifyRequest.mods must not be empty."); 165 166 this.dn = dn; 167 168 modifications = new ArrayList<Modification>(mods); 169 } 170 171 172 173 /** 174 * Creates a new modify request with the provided information. 175 * 176 * @param dn The DN of the entry to modify. It must not be {@code null}. 177 * @param mod The modification to apply to the entry. It must not be 178 * {@code null}. 179 */ 180 public ModifyRequest(final DN dn, final Modification mod) 181 { 182 super(null); 183 184 ensureNotNull(dn, mod); 185 186 this.dn = dn.toString(); 187 188 modifications = new ArrayList<Modification>(1); 189 modifications.add(mod); 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 mods The set of modifications to apply to the entry. It must not 199 * be {@code null} or empty. 200 */ 201 public ModifyRequest(final DN dn, final Modification... mods) 202 { 203 super(null); 204 205 ensureNotNull(dn, mods); 206 ensureFalse(mods.length == 0, 207 "ModifyRequest.mods must not be empty."); 208 209 this.dn = dn.toString(); 210 211 modifications = new ArrayList<Modification>(mods.length); 212 modifications.addAll(Arrays.asList(mods)); 213 } 214 215 216 217 /** 218 * Creates a new modify request with the provided information. 219 * 220 * @param dn The DN of the entry to modify. It must not be {@code null}. 221 * @param mods The set of modifications to apply to the entry. It must not 222 * be {@code null} or empty. 223 */ 224 public ModifyRequest(final DN dn, final List<Modification> mods) 225 { 226 super(null); 227 228 ensureNotNull(dn, mods); 229 ensureFalse(mods.isEmpty(), 230 "ModifyRequest.mods must not be empty."); 231 232 this.dn = dn.toString(); 233 234 modifications = new ArrayList<Modification>(mods); 235 } 236 237 238 239 /** 240 * Creates a new modify request with the provided information. 241 * 242 * @param dn The DN of the entry to modify. It must not be 243 * {@code null}. 244 * @param mod The modification to apply to the entry. It must not be 245 * {@code null}. 246 * @param controls The set of controls to include in the request. 247 */ 248 public ModifyRequest(final String dn, final Modification mod, 249 final Control[] controls) 250 { 251 super(controls); 252 253 ensureNotNull(dn, mod); 254 255 this.dn = dn; 256 257 modifications = new ArrayList<Modification>(1); 258 modifications.add(mod); 259 } 260 261 262 263 /** 264 * Creates a new modify request with the provided information. 265 * 266 * @param dn The DN of the entry to modify. It must not be 267 * {@code null}. 268 * @param mods The set of modifications to apply to the entry. It must 269 * not be {@code null} or empty. 270 * @param controls The set of controls to include in the request. 271 */ 272 public ModifyRequest(final String dn, final Modification[] mods, 273 final Control[] controls) 274 { 275 super(controls); 276 277 ensureNotNull(dn, mods); 278 ensureFalse(mods.length == 0, 279 "ModifyRequest.mods must not be empty."); 280 281 this.dn = dn; 282 283 modifications = new ArrayList<Modification>(mods.length); 284 modifications.addAll(Arrays.asList(mods)); 285 } 286 287 288 289 /** 290 * Creates a new modify request with the provided information. 291 * 292 * @param dn The DN of the entry to modify. It must not be 293 * {@code null}. 294 * @param mods The set of modifications to apply to the entry. It must 295 * not be {@code null} or empty. 296 * @param controls The set of controls to include in the request. 297 */ 298 public ModifyRequest(final String dn, final List<Modification> mods, 299 final Control[] controls) 300 { 301 super(controls); 302 303 ensureNotNull(dn, mods); 304 ensureFalse(mods.isEmpty(), 305 "ModifyRequest.mods must not be empty."); 306 307 this.dn = dn; 308 309 modifications = new ArrayList<Modification>(mods); 310 } 311 312 313 314 /** 315 * Creates a new modify request with the provided information. 316 * 317 * @param dn The DN of the entry to modify. It must not be 318 * {@code null}. 319 * @param mod The modification to apply to the entry. It must not be 320 * {@code null}. 321 * @param controls The set of controls to include in the request. 322 */ 323 public ModifyRequest(final DN dn, final Modification mod, 324 final Control[] controls) 325 { 326 super(controls); 327 328 ensureNotNull(dn, mod); 329 330 this.dn = dn.toString(); 331 332 modifications = new ArrayList<Modification>(1); 333 modifications.add(mod); 334 } 335 336 337 338 /** 339 * Creates a new modify request with the provided information. 340 * 341 * @param dn The DN of the entry to modify. It must not be 342 * {@code null}. 343 * @param mods The set of modifications to apply to the entry. It must 344 * not be {@code null} or empty. 345 * @param controls The set of controls to include in the request. 346 */ 347 public ModifyRequest(final DN dn, final Modification[] mods, 348 final Control[] controls) 349 { 350 super(controls); 351 352 ensureNotNull(dn, mods); 353 ensureFalse(mods.length == 0, 354 "ModifyRequest.mods must not be empty."); 355 356 this.dn = dn.toString(); 357 358 modifications = new ArrayList<Modification>(mods.length); 359 modifications.addAll(Arrays.asList(mods)); 360 } 361 362 363 364 /** 365 * Creates a new modify request with the provided information. 366 * 367 * @param dn The DN of the entry to modify. It must not be 368 * {@code null}. 369 * @param mods The set of modifications to apply to the entry. It must 370 * not be {@code null} or empty. 371 * @param controls The set of controls to include in the request. 372 */ 373 public ModifyRequest(final DN dn, final List<Modification> mods, 374 final Control[] controls) 375 { 376 super(controls); 377 378 ensureNotNull(dn, mods); 379 ensureFalse(mods.isEmpty(), 380 "ModifyRequest.mods must not be empty."); 381 382 this.dn = dn.toString(); 383 384 modifications = new ArrayList<Modification>(mods); 385 } 386 387 388 389 /** 390 * Creates a new modify request from the provided LDIF representation of the 391 * changes. 392 * 393 * @param ldifModificationLines The lines that comprise an LDIF 394 * representation of a modify change record. 395 * It must not be {@code null} or empty. 396 * 397 * @throws LDIFException If the provided set of lines cannot be parsed as an 398 * LDIF modify change record. 399 */ 400 public ModifyRequest(final String... ldifModificationLines) 401 throws LDIFException 402 { 403 super(null); 404 405 final LDIFChangeRecord changeRecord = 406 LDIFReader.decodeChangeRecord(ldifModificationLines); 407 if (! (changeRecord instanceof LDIFModifyChangeRecord)) 408 { 409 throw new LDIFException(ERR_MODIFY_INVALID_LDIF.get(), 0, false, 410 ldifModificationLines, null); 411 } 412 413 final LDIFModifyChangeRecord modifyRecord = 414 (LDIFModifyChangeRecord) changeRecord; 415 final ModifyRequest r = modifyRecord.toModifyRequest(); 416 417 dn = r.dn; 418 modifications = r.modifications; 419 } 420 421 422 423 /** 424 * {@inheritDoc} 425 */ 426 public String getDN() 427 { 428 return dn; 429 } 430 431 432 433 /** 434 * Specifies the DN of the entry to modify. 435 * 436 * @param dn The DN of the entry to modify. It must not be {@code null}. 437 */ 438 public void setDN(final String dn) 439 { 440 ensureNotNull(dn); 441 442 this.dn = dn; 443 } 444 445 446 447 /** 448 * Specifies the DN of the entry to modify. 449 * 450 * @param dn The DN of the entry to modify. It must not be {@code null}. 451 */ 452 public void setDN(final DN dn) 453 { 454 ensureNotNull(dn); 455 456 this.dn = dn.toString(); 457 } 458 459 460 461 /** 462 * {@inheritDoc} 463 */ 464 public List<Modification> getModifications() 465 { 466 return Collections.unmodifiableList(modifications); 467 } 468 469 470 471 /** 472 * Adds the provided modification to the set of modifications for this modify 473 * request. 474 * 475 * @param mod The modification to be added. It must not be {@code null}. 476 */ 477 public void addModification(final Modification mod) 478 { 479 ensureNotNull(mod); 480 481 modifications.add(mod); 482 } 483 484 485 486 /** 487 * Removes the provided modification from the set of modifications for this 488 * modify request. 489 * 490 * @param mod The modification to be removed. It must not be {@code null}. 491 * 492 * @return {@code true} if the specified modification was found and removed, 493 * or {@code false} if not. 494 */ 495 public boolean removeModification(final Modification mod) 496 { 497 ensureNotNull(mod); 498 499 return modifications.remove(mod); 500 } 501 502 503 504 /** 505 * Replaces the existing set of modifications for this modify request with the 506 * provided modification. 507 * 508 * @param mod The modification to use for this modify request. It must not 509 * be {@code null}. 510 */ 511 public void setModifications(final Modification mod) 512 { 513 ensureNotNull(mod); 514 515 modifications.clear(); 516 modifications.add(mod); 517 } 518 519 520 521 /** 522 * Replaces the existing set of modifications for this modify request with the 523 * provided modifications. 524 * 525 * @param mods The set of modification to use for this modify request. It 526 * must not be {@code null} or empty. 527 */ 528 public void setModifications(final Modification[] mods) 529 { 530 ensureNotNull(mods); 531 ensureFalse(mods.length == 0, 532 "ModifyRequest.setModifications.mods must not be empty."); 533 534 modifications.clear(); 535 modifications.addAll(Arrays.asList(mods)); 536 } 537 538 539 540 /** 541 * Replaces the existing set of modifications for this modify request with the 542 * provided modifications. 543 * 544 * @param mods The set of modification to use for this modify request. It 545 * must not be {@code null} or empty. 546 */ 547 public void setModifications(final List<Modification> mods) 548 { 549 ensureNotNull(mods); 550 ensureFalse(mods.isEmpty(), 551 "ModifyRequest.setModifications.mods must not be empty."); 552 553 modifications.clear(); 554 modifications.addAll(mods); 555 } 556 557 558 559 /** 560 * {@inheritDoc} 561 */ 562 public byte getProtocolOpType() 563 { 564 return LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_REQUEST; 565 } 566 567 568 569 /** 570 * {@inheritDoc} 571 */ 572 public void writeTo(final ASN1Buffer writer) 573 { 574 final ASN1BufferSequence requestSequence = 575 writer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_REQUEST); 576 writer.addOctetString(dn); 577 578 final ASN1BufferSequence modSequence = writer.beginSequence(); 579 for (final Modification m : modifications) 580 { 581 m.writeTo(writer); 582 } 583 modSequence.end(); 584 requestSequence.end(); 585 } 586 587 588 589 /** 590 * Encodes the modify request protocol op to an ASN.1 element. 591 * 592 * @return The ASN.1 element with the encoded modify request protocol op. 593 */ 594 public ASN1Element encodeProtocolOp() 595 { 596 final ASN1Element[] modElements = new ASN1Element[modifications.size()]; 597 for (int i=0; i < modElements.length; i++) 598 { 599 modElements[i] = modifications.get(i).encode(); 600 } 601 602 final ASN1Element[] protocolOpElements = 603 { 604 new ASN1OctetString(dn), 605 new ASN1Sequence(modElements) 606 }; 607 608 609 610 return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_REQUEST, 611 protocolOpElements); 612 } 613 614 615 616 /** 617 * Sends this modify request to the directory server over the provided 618 * connection and returns the associated response. 619 * 620 * @param connection The connection to use to communicate with the directory 621 * server. 622 * @param depth The current referral depth for this request. It should 623 * always be one for the initial request, and should only 624 * be incremented when following referrals. 625 * 626 * @return An LDAP result object that provides information about the result 627 * of the modify processing. 628 * 629 * @throws LDAPException If a problem occurs while sending the request or 630 * reading the response. 631 */ 632 @Override() 633 protected LDAPResult process(final LDAPConnection connection, final int depth) 634 throws LDAPException 635 { 636 if (connection.synchronousMode()) 637 { 638 @SuppressWarnings("deprecation") 639 final boolean autoReconnect = 640 connection.getConnectionOptions().autoReconnect(); 641 return processSync(connection, depth, autoReconnect); 642 } 643 644 final long requestTime = System.nanoTime(); 645 processAsync(connection, null); 646 647 try 648 { 649 // Wait for and process the response. 650 final LDAPResponse response; 651 try 652 { 653 final long responseTimeout = getResponseTimeoutMillis(connection); 654 if (responseTimeout > 0) 655 { 656 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS); 657 } 658 else 659 { 660 response = responseQueue.take(); 661 } 662 } 663 catch (InterruptedException ie) 664 { 665 debugException(ie); 666 throw new LDAPException(ResultCode.LOCAL_ERROR, 667 ERR_MODIFY_INTERRUPTED.get(connection.getHostPort()), ie); 668 } 669 670 return handleResponse(connection, response, requestTime, depth, false); 671 } 672 finally 673 { 674 connection.deregisterResponseAcceptor(messageID); 675 } 676 } 677 678 679 680 /** 681 * Sends this modify request to the directory server over the provided 682 * connection and returns the message ID for the request. 683 * 684 * @param connection The connection to use to communicate with the 685 * directory server. 686 * @param resultListener The async result listener that is to be notified 687 * when the response is received. It may be 688 * {@code null} only if the result is to be processed 689 * by this class. 690 * 691 * @return The async request ID created for the operation, or {@code null} if 692 * the provided {@code resultListener} is {@code null} and the 693 * operation will not actually be processed asynchronously. 694 * 695 * @throws LDAPException If a problem occurs while sending the request. 696 */ 697 AsyncRequestID processAsync(final LDAPConnection connection, 698 final AsyncResultListener resultListener) 699 throws LDAPException 700 { 701 // Create the LDAP message. 702 messageID = connection.nextMessageID(); 703 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 704 705 706 // If the provided async result listener is {@code null}, then we'll use 707 // this class as the message acceptor. Otherwise, create an async helper 708 // and use it as the message acceptor. 709 final AsyncRequestID asyncRequestID; 710 if (resultListener == null) 711 { 712 asyncRequestID = null; 713 connection.registerResponseAcceptor(messageID, this); 714 } 715 else 716 { 717 final AsyncHelper helper = new AsyncHelper(connection, 718 OperationType.MODIFY, messageID, resultListener, 719 getIntermediateResponseListener()); 720 connection.registerResponseAcceptor(messageID, helper); 721 asyncRequestID = helper.getAsyncRequestID(); 722 723 final long timeout = getResponseTimeoutMillis(connection); 724 if (timeout > 0L) 725 { 726 final Timer timer = connection.getTimer(); 727 final AsyncTimeoutTimerTask timerTask = 728 new AsyncTimeoutTimerTask(helper); 729 timer.schedule(timerTask, timeout); 730 asyncRequestID.setTimerTask(timerTask); 731 } 732 } 733 734 735 // Send the request to the server. 736 try 737 { 738 debugLDAPRequest(this); 739 connection.getConnectionStatistics().incrementNumModifyRequests(); 740 connection.sendMessage(message); 741 return asyncRequestID; 742 } 743 catch (LDAPException le) 744 { 745 debugException(le); 746 747 connection.deregisterResponseAcceptor(messageID); 748 throw le; 749 } 750 } 751 752 753 754 /** 755 * Processes this modify operation in synchronous mode, in which the same 756 * thread will send the request and read the response. 757 * 758 * @param connection The connection to use to communicate with the directory 759 * server. 760 * @param depth The current referral depth for this request. It should 761 * always be one for the initial request, and should only 762 * be incremented when following referrals. 763 * @param allowRetry Indicates whether the request may be re-tried on a 764 * re-established connection if the initial attempt fails 765 * in a way that indicates the connection is no longer 766 * valid and autoReconnect is true. 767 * 768 * @return An LDAP result object that provides information about the result 769 * of the modify processing. 770 * 771 * @throws LDAPException If a problem occurs while sending the request or 772 * reading the response. 773 */ 774 private LDAPResult processSync(final LDAPConnection connection, 775 final int depth, final boolean allowRetry) 776 throws LDAPException 777 { 778 // Create the LDAP message. 779 messageID = connection.nextMessageID(); 780 final LDAPMessage message = 781 new LDAPMessage(messageID, this, getControls()); 782 783 784 // Set the appropriate timeout on the socket. 785 try 786 { 787 connection.getConnectionInternals(true).getSocket().setSoTimeout( 788 (int) getResponseTimeoutMillis(connection)); 789 } 790 catch (Exception e) 791 { 792 debugException(e); 793 } 794 795 796 // Send the request to the server. 797 final long requestTime = System.nanoTime(); 798 debugLDAPRequest(this); 799 connection.getConnectionStatistics().incrementNumModifyRequests(); 800 try 801 { 802 connection.sendMessage(message); 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 = connection.getReferralConnector(). 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 (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 public void responseReceived(final LDAPResponse response) 1087 throws LDAPException 1088 { 1089 try 1090 { 1091 responseQueue.put(response); 1092 } 1093 catch (Exception e) 1094 { 1095 debugException(e); 1096 throw new LDAPException(ResultCode.LOCAL_ERROR, 1097 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); 1098 } 1099 } 1100 1101 1102 1103 /** 1104 * {@inheritDoc} 1105 */ 1106 @Override() 1107 public int getLastMessageID() 1108 { 1109 return messageID; 1110 } 1111 1112 1113 1114 /** 1115 * {@inheritDoc} 1116 */ 1117 @Override() 1118 public OperationType getOperationType() 1119 { 1120 return OperationType.MODIFY; 1121 } 1122 1123 1124 1125 /** 1126 * {@inheritDoc} 1127 */ 1128 public ModifyRequest duplicate() 1129 { 1130 return duplicate(getControls()); 1131 } 1132 1133 1134 1135 /** 1136 * {@inheritDoc} 1137 */ 1138 public ModifyRequest duplicate(final Control[] controls) 1139 { 1140 final ModifyRequest r = new ModifyRequest(dn, 1141 new ArrayList<Modification>(modifications), controls); 1142 1143 if (followReferralsInternal() != null) 1144 { 1145 r.setFollowReferrals(followReferralsInternal()); 1146 } 1147 1148 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 1149 1150 return r; 1151 } 1152 1153 1154 1155 /** 1156 * {@inheritDoc} 1157 */ 1158 public LDIFModifyChangeRecord toLDIFChangeRecord() 1159 { 1160 return new LDIFModifyChangeRecord(this); 1161 } 1162 1163 1164 1165 /** 1166 * {@inheritDoc} 1167 */ 1168 public String[] toLDIF() 1169 { 1170 return toLDIFChangeRecord().toLDIF(); 1171 } 1172 1173 1174 1175 /** 1176 * {@inheritDoc} 1177 */ 1178 public String toLDIFString() 1179 { 1180 return toLDIFChangeRecord().toLDIFString(); 1181 } 1182 1183 1184 1185 /** 1186 * {@inheritDoc} 1187 */ 1188 @Override() 1189 public void toString(final StringBuilder buffer) 1190 { 1191 buffer.append("ModifyRequest(dn='"); 1192 buffer.append(dn); 1193 buffer.append("', mods={"); 1194 for (int i=0; i < modifications.size(); i++) 1195 { 1196 final Modification m = modifications.get(i); 1197 1198 if (i > 0) 1199 { 1200 buffer.append(", "); 1201 } 1202 1203 switch (m.getModificationType().intValue()) 1204 { 1205 case 0: 1206 buffer.append("ADD "); 1207 break; 1208 1209 case 1: 1210 buffer.append("DELETE "); 1211 break; 1212 1213 case 2: 1214 buffer.append("REPLACE "); 1215 break; 1216 1217 case 3: 1218 buffer.append("INCREMENT "); 1219 break; 1220 } 1221 1222 buffer.append(m.getAttributeName()); 1223 } 1224 buffer.append('}'); 1225 1226 final Control[] controls = getControls(); 1227 if (controls.length > 0) 1228 { 1229 buffer.append(", controls={"); 1230 for (int i=0; i < controls.length; i++) 1231 { 1232 if (i > 0) 1233 { 1234 buffer.append(", "); 1235 } 1236 1237 buffer.append(controls[i]); 1238 } 1239 buffer.append('}'); 1240 } 1241 1242 buffer.append(')'); 1243 } 1244 1245 1246 1247 /** 1248 * {@inheritDoc} 1249 */ 1250 public void toCode(final List<String> lineList, final String requestID, 1251 final int indentSpaces, final boolean includeProcessing) 1252 { 1253 // Create the request variable. 1254 final ArrayList<ToCodeArgHelper> constructorArgs = 1255 new ArrayList<ToCodeArgHelper>(modifications.size() + 1); 1256 constructorArgs.add(ToCodeArgHelper.createString(dn, "Entry DN")); 1257 1258 boolean firstMod = true; 1259 for (final Modification m : modifications) 1260 { 1261 final String comment; 1262 if (firstMod) 1263 { 1264 firstMod = false; 1265 comment = "Modifications"; 1266 } 1267 else 1268 { 1269 comment = null; 1270 } 1271 1272 constructorArgs.add(ToCodeArgHelper.createModification(m, comment)); 1273 } 1274 1275 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ModifyRequest", 1276 requestID + "Request", "new ModifyRequest", constructorArgs); 1277 1278 1279 // If there are any controls, then add them to the request. 1280 for (final Control c : getControls()) 1281 { 1282 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1283 requestID + "Request.addControl", 1284 ToCodeArgHelper.createControl(c, null)); 1285 } 1286 1287 1288 // Add lines for processing the request and obtaining the result. 1289 if (includeProcessing) 1290 { 1291 // Generate a string with the appropriate indent. 1292 final StringBuilder buffer = new StringBuilder(); 1293 for (int i=0; i < indentSpaces; i++) 1294 { 1295 buffer.append(' '); 1296 } 1297 final String indent = buffer.toString(); 1298 1299 lineList.add(""); 1300 lineList.add(indent + "try"); 1301 lineList.add(indent + '{'); 1302 lineList.add(indent + " LDAPResult " + requestID + 1303 "Result = connection.modify(" + requestID + "Request);"); 1304 lineList.add(indent + " // The modify was processed successfully."); 1305 lineList.add(indent + '}'); 1306 lineList.add(indent + "catch (LDAPException e)"); 1307 lineList.add(indent + '{'); 1308 lineList.add(indent + " // The modify failed. Maybe the following " + 1309 "will help explain why."); 1310 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 1311 lineList.add(indent + " String message = e.getMessage();"); 1312 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 1313 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 1314 lineList.add(indent + " Control[] responseControls = " + 1315 "e.getResponseControls();"); 1316 lineList.add(indent + '}'); 1317 } 1318 } 1319 }