001/* 002 * Copyright 2007-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2019 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.List; 026import java.util.Timer; 027import java.util.concurrent.LinkedBlockingQueue; 028import java.util.concurrent.TimeUnit; 029import java.util.logging.Level; 030 031import com.unboundid.asn1.ASN1Buffer; 032import com.unboundid.asn1.ASN1Element; 033import com.unboundid.asn1.ASN1OctetString; 034import com.unboundid.ldap.protocol.LDAPMessage; 035import com.unboundid.ldap.protocol.LDAPResponse; 036import com.unboundid.ldap.protocol.ProtocolOp; 037import com.unboundid.ldif.LDIFDeleteChangeRecord; 038import com.unboundid.util.Debug; 039import com.unboundid.util.InternalUseOnly; 040import com.unboundid.util.Mutable; 041import com.unboundid.util.StaticUtils; 042import com.unboundid.util.ThreadSafety; 043import com.unboundid.util.ThreadSafetyLevel; 044import com.unboundid.util.Validator; 045 046import static com.unboundid.ldap.sdk.LDAPMessages.*; 047 048 049 050/** 051 * This class implements the processing necessary to perform an LDAPv3 delete 052 * operation, which removes an entry from the directory. A delete request 053 * contains the DN of the entry to remove. It may also include a set of 054 * controls to send to the server. 055 * {@code DeleteRequest} objects are mutable and therefore can be altered and 056 * re-used for multiple requests. Note, however, that {@code DeleteRequest} 057 * objects are not threadsafe and therefore a single {@code DeleteRequest} 058 * object instance should not be used to process multiple requests at the same 059 * time. 060 * <BR><BR> 061 * <H2>Example</H2> 062 * The following example demonstrates the process for performing a delete 063 * operation: 064 * <PRE> 065 * DeleteRequest deleteRequest = 066 * new DeleteRequest("cn=entry to delete,dc=example,dc=com"); 067 * LDAPResult deleteResult; 068 * try 069 * { 070 * deleteResult = connection.delete(deleteRequest); 071 * // If we get here, the delete was successful. 072 * } 073 * catch (LDAPException le) 074 * { 075 * // The delete operation failed. 076 * deleteResult = le.toLDAPResult(); 077 * ResultCode resultCode = le.getResultCode(); 078 * String errorMessageFromServer = le.getDiagnosticMessage(); 079 * } 080 * </PRE> 081 */ 082@Mutable() 083@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 084public final class DeleteRequest 085 extends UpdatableLDAPRequest 086 implements ReadOnlyDeleteRequest, ResponseAcceptor, ProtocolOp 087{ 088 /** 089 * The serial version UID for this serializable class. 090 */ 091 private static final long serialVersionUID = -6126029442850884239L; 092 093 094 095 // The message ID from the last LDAP message sent from this request. 096 private int messageID = -1; 097 098 // The queue that will be used to receive response messages from the server. 099 private final LinkedBlockingQueue<LDAPResponse> responseQueue = 100 new LinkedBlockingQueue<>(); 101 102 // The DN of the entry to delete. 103 private String dn; 104 105 106 107 /** 108 * Creates a new delete request with the provided DN. 109 * 110 * @param dn The DN of the entry to delete. It must not be {@code null}. 111 */ 112 public DeleteRequest(final String dn) 113 { 114 super(null); 115 116 Validator.ensureNotNull(dn); 117 118 this.dn = dn; 119 } 120 121 122 123 /** 124 * Creates a new delete request with the provided DN. 125 * 126 * @param dn The DN of the entry to delete. It must not be 127 * {@code null}. 128 * @param controls The set of controls to include in the request. 129 */ 130 public DeleteRequest(final String dn, final Control[] controls) 131 { 132 super(controls); 133 134 Validator.ensureNotNull(dn); 135 136 this.dn = dn; 137 } 138 139 140 141 /** 142 * Creates a new delete request with the provided DN. 143 * 144 * @param dn The DN of the entry to delete. It must not be {@code null}. 145 */ 146 public DeleteRequest(final DN dn) 147 { 148 super(null); 149 150 Validator.ensureNotNull(dn); 151 152 this.dn = dn.toString(); 153 } 154 155 156 157 /** 158 * Creates a new delete request with the provided DN. 159 * 160 * @param dn The DN of the entry to delete. It must not be 161 * {@code null}. 162 * @param controls The set of controls to include in the request. 163 */ 164 public DeleteRequest(final DN dn, final Control[] controls) 165 { 166 super(controls); 167 168 Validator.ensureNotNull(dn); 169 170 this.dn = dn.toString(); 171 } 172 173 174 175 /** 176 * {@inheritDoc} 177 */ 178 @Override() 179 public String getDN() 180 { 181 return dn; 182 } 183 184 185 186 /** 187 * Specifies the DN of the entry to delete. 188 * 189 * @param dn The DN of the entry to delete. It must not be {@code null}. 190 */ 191 public void setDN(final String dn) 192 { 193 Validator.ensureNotNull(dn); 194 195 this.dn = dn; 196 } 197 198 199 200 /** 201 * Specifies the DN of the entry to delete. 202 * 203 * @param dn The DN of the entry to delete. It must not be {@code null}. 204 */ 205 public void setDN(final DN dn) 206 { 207 Validator.ensureNotNull(dn); 208 209 this.dn = dn.toString(); 210 } 211 212 213 214 /** 215 * {@inheritDoc} 216 */ 217 @Override() 218 public byte getProtocolOpType() 219 { 220 return LDAPMessage.PROTOCOL_OP_TYPE_DELETE_REQUEST; 221 } 222 223 224 225 /** 226 * {@inheritDoc} 227 */ 228 @Override() 229 public void writeTo(final ASN1Buffer buffer) 230 { 231 buffer.addOctetString(LDAPMessage.PROTOCOL_OP_TYPE_DELETE_REQUEST, dn); 232 } 233 234 235 236 /** 237 * Encodes the delete request protocol op to an ASN.1 element. 238 * 239 * @return The ASN.1 element with the encoded delete request protocol op. 240 */ 241 @Override() 242 public ASN1Element encodeProtocolOp() 243 { 244 return new ASN1OctetString(LDAPMessage.PROTOCOL_OP_TYPE_DELETE_REQUEST, dn); 245 } 246 247 248 249 /** 250 * Sends this delete request to the directory server over the provided 251 * connection and returns the associated response. 252 * 253 * @param connection The connection to use to communicate with the directory 254 * server. 255 * @param depth The current referral depth for this request. It should 256 * always be one for the initial request, and should only 257 * be incremented when following referrals. 258 * 259 * @return An LDAP result object that provides information about the result 260 * of the delete processing. 261 * 262 * @throws LDAPException If a problem occurs while sending the request or 263 * reading the response. 264 */ 265 @Override() 266 protected LDAPResult process(final LDAPConnection connection, final int depth) 267 throws LDAPException 268 { 269 if (connection.synchronousMode()) 270 { 271 @SuppressWarnings("deprecation") 272 final boolean autoReconnect = 273 connection.getConnectionOptions().autoReconnect(); 274 return processSync(connection, depth, autoReconnect); 275 } 276 277 final long requestTime = System.nanoTime(); 278 processAsync(connection, null); 279 280 try 281 { 282 // Wait for and process the response. 283 final LDAPResponse response; 284 try 285 { 286 final long responseTimeout = getResponseTimeoutMillis(connection); 287 if (responseTimeout > 0) 288 { 289 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS); 290 } 291 else 292 { 293 response = responseQueue.take(); 294 } 295 } 296 catch (final InterruptedException ie) 297 { 298 Debug.debugException(ie); 299 Thread.currentThread().interrupt(); 300 throw new LDAPException(ResultCode.LOCAL_ERROR, 301 ERR_DELETE_INTERRUPTED.get(connection.getHostPort()), ie); 302 } 303 304 return handleResponse(connection, response, requestTime, depth, false); 305 } 306 finally 307 { 308 connection.deregisterResponseAcceptor(messageID); 309 } 310 } 311 312 313 314 /** 315 * Sends this delete request to the directory server over the provided 316 * connection and returns the message ID for the request. 317 * 318 * @param connection The connection to use to communicate with the 319 * directory server. 320 * @param resultListener The async result listener that is to be notified 321 * when the response is received. It may be 322 * {@code null} only if the result is to be processed 323 * by this class. 324 * 325 * @return The async request ID created for the operation, or {@code null} if 326 * the provided {@code resultListener} is {@code null} and the 327 * operation will not actually be processed asynchronously. 328 * 329 * @throws LDAPException If a problem occurs while sending the request. 330 */ 331 AsyncRequestID processAsync(final LDAPConnection connection, 332 final AsyncResultListener resultListener) 333 throws LDAPException 334 { 335 // Create the LDAP message. 336 messageID = connection.nextMessageID(); 337 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 338 339 340 // If the provided async result listener is {@code null}, then we'll use 341 // this class as the message acceptor. Otherwise, create an async helper 342 // and use it as the message acceptor. 343 final AsyncRequestID asyncRequestID; 344 final long timeout = getResponseTimeoutMillis(connection); 345 if (resultListener == null) 346 { 347 asyncRequestID = null; 348 connection.registerResponseAcceptor(messageID, this); 349 } 350 else 351 { 352 final AsyncHelper helper = new AsyncHelper(connection, 353 OperationType.DELETE, messageID, resultListener, 354 getIntermediateResponseListener()); 355 connection.registerResponseAcceptor(messageID, helper); 356 asyncRequestID = helper.getAsyncRequestID(); 357 358 if (timeout > 0L) 359 { 360 final Timer timer = connection.getTimer(); 361 final AsyncTimeoutTimerTask timerTask = 362 new AsyncTimeoutTimerTask(helper); 363 timer.schedule(timerTask, timeout); 364 asyncRequestID.setTimerTask(timerTask); 365 } 366 } 367 368 369 // Send the request to the server. 370 try 371 { 372 Debug.debugLDAPRequest(Level.INFO, this, messageID, connection); 373 connection.getConnectionStatistics().incrementNumDeleteRequests(); 374 connection.sendMessage(message, timeout); 375 return asyncRequestID; 376 } 377 catch (final LDAPException le) 378 { 379 Debug.debugException(le); 380 381 connection.deregisterResponseAcceptor(messageID); 382 throw le; 383 } 384 } 385 386 387 388 /** 389 * Processes this delete operation in synchronous mode, in which the same 390 * thread will send the request and read the response. 391 * 392 * @param connection The connection to use to communicate with the directory 393 * server. 394 * @param depth The current referral depth for this request. It should 395 * always be one for the initial request, and should only 396 * be incremented when following referrals. 397 * @param allowRetry Indicates whether the request may be re-tried on a 398 * re-established connection if the initial attempt fails 399 * in a way that indicates the connection is no longer 400 * valid and autoReconnect is true. 401 * 402 * @return An LDAP result object that provides information about the result 403 * of the delete processing. 404 * 405 * @throws LDAPException If a problem occurs while sending the request or 406 * reading the response. 407 */ 408 private LDAPResult processSync(final LDAPConnection connection, 409 final int depth, final boolean allowRetry) 410 throws LDAPException 411 { 412 // Create the LDAP message. 413 messageID = connection.nextMessageID(); 414 final LDAPMessage message = 415 new LDAPMessage(messageID, this, getControls()); 416 417 418 // Send the request to the server. 419 final long requestTime = System.nanoTime(); 420 Debug.debugLDAPRequest(Level.INFO, this, messageID, connection); 421 connection.getConnectionStatistics().incrementNumDeleteRequests(); 422 try 423 { 424 connection.sendMessage(message, getResponseTimeoutMillis(connection)); 425 } 426 catch (final LDAPException le) 427 { 428 Debug.debugException(le); 429 430 if (allowRetry) 431 { 432 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 433 le.getResultCode()); 434 if (retryResult != null) 435 { 436 return retryResult; 437 } 438 } 439 440 throw le; 441 } 442 443 while (true) 444 { 445 final LDAPResponse response; 446 try 447 { 448 response = connection.readResponse(messageID); 449 } 450 catch (final LDAPException le) 451 { 452 Debug.debugException(le); 453 454 if ((le.getResultCode() == ResultCode.TIMEOUT) && 455 connection.getConnectionOptions().abandonOnTimeout()) 456 { 457 connection.abandon(messageID); 458 } 459 460 if (allowRetry) 461 { 462 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 463 le.getResultCode()); 464 if (retryResult != null) 465 { 466 return retryResult; 467 } 468 } 469 470 throw le; 471 } 472 473 if (response instanceof IntermediateResponse) 474 { 475 final IntermediateResponseListener listener = 476 getIntermediateResponseListener(); 477 if (listener != null) 478 { 479 listener.intermediateResponseReturned( 480 (IntermediateResponse) response); 481 } 482 } 483 else 484 { 485 return handleResponse(connection, response, requestTime, depth, 486 allowRetry); 487 } 488 } 489 } 490 491 492 493 /** 494 * Performs the necessary processing for handling a response. 495 * 496 * @param connection The connection used to read the response. 497 * @param response The response to be processed. 498 * @param requestTime The time the request was sent to the server. 499 * @param depth The current referral depth for this request. It 500 * should always be one for the initial request, and 501 * should only be incremented when following referrals. 502 * @param allowRetry Indicates whether the request may be re-tried on a 503 * re-established connection if the initial attempt fails 504 * in a way that indicates the connection is no longer 505 * valid and autoReconnect is true. 506 * 507 * @return The delete result. 508 * 509 * @throws LDAPException If a problem occurs. 510 */ 511 private LDAPResult handleResponse(final LDAPConnection connection, 512 final LDAPResponse response, 513 final long requestTime, final int depth, 514 final boolean allowRetry) 515 throws LDAPException 516 { 517 if (response == null) 518 { 519 final long waitTime = 520 StaticUtils.nanosToMillis(System.nanoTime() - requestTime); 521 if (connection.getConnectionOptions().abandonOnTimeout()) 522 { 523 connection.abandon(messageID); 524 } 525 526 throw new LDAPException(ResultCode.TIMEOUT, 527 ERR_DELETE_CLIENT_TIMEOUT.get(waitTime, messageID, dn, 528 connection.getHostPort())); 529 } 530 531 connection.getConnectionStatistics().incrementNumDeleteResponses( 532 System.nanoTime() - requestTime); 533 if (response instanceof ConnectionClosedResponse) 534 { 535 // The connection was closed while waiting for the response. 536 if (allowRetry) 537 { 538 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 539 ResultCode.SERVER_DOWN); 540 if (retryResult != null) 541 { 542 return retryResult; 543 } 544 } 545 546 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 547 final String message = ccr.getMessage(); 548 if (message == null) 549 { 550 throw new LDAPException(ccr.getResultCode(), 551 ERR_CONN_CLOSED_WAITING_FOR_DELETE_RESPONSE.get( 552 connection.getHostPort(), toString())); 553 } 554 else 555 { 556 throw new LDAPException(ccr.getResultCode(), 557 ERR_CONN_CLOSED_WAITING_FOR_DELETE_RESPONSE_WITH_MESSAGE.get( 558 connection.getHostPort(), toString(), message)); 559 } 560 } 561 562 final LDAPResult result = (LDAPResult) response; 563 if ((result.getResultCode().equals(ResultCode.REFERRAL)) && 564 followReferrals(connection)) 565 { 566 if (depth >= connection.getConnectionOptions().getReferralHopLimit()) 567 { 568 return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED, 569 ERR_TOO_MANY_REFERRALS.get(), 570 result.getMatchedDN(), result.getReferralURLs(), 571 result.getResponseControls()); 572 } 573 574 return followReferral(result, connection, depth); 575 } 576 else 577 { 578 if (allowRetry) 579 { 580 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 581 result.getResultCode()); 582 if (retryResult != null) 583 { 584 return retryResult; 585 } 586 } 587 588 return result; 589 } 590 } 591 592 593 594 /** 595 * Attempts to re-establish the connection and retry processing this request 596 * on it. 597 * 598 * @param connection The connection to be re-established. 599 * @param depth The current referral depth for this request. It should 600 * always be one for the initial request, and should only 601 * be incremented when following referrals. 602 * @param resultCode The result code for the previous operation attempt. 603 * 604 * @return The result from re-trying the add, or {@code null} if it could not 605 * be re-tried. 606 */ 607 private LDAPResult reconnectAndRetry(final LDAPConnection connection, 608 final int depth, 609 final ResultCode resultCode) 610 { 611 try 612 { 613 // We will only want to retry for certain result codes that indicate a 614 // connection problem. 615 switch (resultCode.intValue()) 616 { 617 case ResultCode.SERVER_DOWN_INT_VALUE: 618 case ResultCode.DECODING_ERROR_INT_VALUE: 619 case ResultCode.CONNECT_ERROR_INT_VALUE: 620 connection.reconnect(); 621 return processSync(connection, depth, false); 622 } 623 } 624 catch (final Exception e) 625 { 626 Debug.debugException(e); 627 } 628 629 return null; 630 } 631 632 633 634 /** 635 * Attempts to follow a referral to perform a delete operation in the target 636 * server. 637 * 638 * @param referralResult The LDAP result object containing information about 639 * the referral to follow. 640 * @param connection The connection on which the referral was received. 641 * @param depth The number of referrals followed in the course of 642 * processing this request. 643 * 644 * @return The result of attempting to process the delete operation by 645 * following the referral. 646 * 647 * @throws LDAPException If a problem occurs while attempting to establish 648 * the referral connection, sending the request, or 649 * reading the result. 650 */ 651 private LDAPResult followReferral(final LDAPResult referralResult, 652 final LDAPConnection connection, 653 final int depth) 654 throws LDAPException 655 { 656 for (final String urlString : referralResult.getReferralURLs()) 657 { 658 try 659 { 660 final LDAPURL referralURL = new LDAPURL(urlString); 661 final String host = referralURL.getHost(); 662 663 if (host == null) 664 { 665 // We can't handle a referral in which there is no host. 666 continue; 667 } 668 669 final DeleteRequest deleteRequest; 670 if (referralURL.baseDNProvided()) 671 { 672 deleteRequest = new DeleteRequest(referralURL.getBaseDN(), 673 getControls()); 674 } 675 else 676 { 677 deleteRequest = this; 678 } 679 680 final LDAPConnection referralConn = getReferralConnector(connection). 681 getReferralConnection(referralURL, connection); 682 try 683 { 684 return deleteRequest.process(referralConn, depth+1); 685 } 686 finally 687 { 688 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null); 689 referralConn.close(); 690 } 691 } 692 catch (final LDAPException le) 693 { 694 Debug.debugException(le); 695 } 696 } 697 698 // If we've gotten here, then we could not follow any of the referral URLs, 699 // so we'll just return the original referral result. 700 return referralResult; 701 } 702 703 704 705 /** 706 * {@inheritDoc} 707 */ 708 @InternalUseOnly() 709 @Override() 710 public void responseReceived(final LDAPResponse response) 711 throws LDAPException 712 { 713 try 714 { 715 responseQueue.put(response); 716 } 717 catch (final Exception e) 718 { 719 Debug.debugException(e); 720 721 if (e instanceof InterruptedException) 722 { 723 Thread.currentThread().interrupt(); 724 } 725 726 throw new LDAPException(ResultCode.LOCAL_ERROR, 727 ERR_EXCEPTION_HANDLING_RESPONSE.get( 728 StaticUtils.getExceptionMessage(e)), 729 e); 730 } 731 } 732 733 734 735 /** 736 * {@inheritDoc} 737 */ 738 @Override() 739 public int getLastMessageID() 740 { 741 return messageID; 742 } 743 744 745 746 /** 747 * {@inheritDoc} 748 */ 749 @Override() 750 public OperationType getOperationType() 751 { 752 return OperationType.DELETE; 753 } 754 755 756 757 /** 758 * {@inheritDoc} 759 */ 760 @Override() 761 public DeleteRequest duplicate() 762 { 763 return duplicate(getControls()); 764 } 765 766 767 768 /** 769 * {@inheritDoc} 770 */ 771 @Override() 772 public DeleteRequest duplicate(final Control[] controls) 773 { 774 final DeleteRequest r = new DeleteRequest(dn, controls); 775 776 if (followReferralsInternal() != null) 777 { 778 r.setFollowReferrals(followReferralsInternal()); 779 } 780 781 if (getReferralConnectorInternal() != null) 782 { 783 r.setReferralConnector(getReferralConnectorInternal()); 784 } 785 786 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 787 788 return r; 789 } 790 791 792 793 /** 794 * {@inheritDoc} 795 */ 796 @Override() 797 public LDIFDeleteChangeRecord toLDIFChangeRecord() 798 { 799 return new LDIFDeleteChangeRecord(this); 800 } 801 802 803 804 /** 805 * {@inheritDoc} 806 */ 807 @Override() 808 public String[] toLDIF() 809 { 810 return toLDIFChangeRecord().toLDIF(); 811 } 812 813 814 815 /** 816 * {@inheritDoc} 817 */ 818 @Override() 819 public String toLDIFString() 820 { 821 return toLDIFChangeRecord().toLDIFString(); 822 } 823 824 825 826 /** 827 * {@inheritDoc} 828 */ 829 @Override() 830 public void toString(final StringBuilder buffer) 831 { 832 buffer.append("DeleteRequest(dn='"); 833 buffer.append(dn); 834 buffer.append('\''); 835 836 final Control[] controls = getControls(); 837 if (controls.length > 0) 838 { 839 buffer.append(", controls={"); 840 for (int i=0; i < controls.length; i++) 841 { 842 if (i > 0) 843 { 844 buffer.append(", "); 845 } 846 847 buffer.append(controls[i]); 848 } 849 buffer.append('}'); 850 } 851 852 buffer.append(')'); 853 } 854 855 856 857 /** 858 * {@inheritDoc} 859 */ 860 @Override() 861 public void toCode(final List<String> lineList, final String requestID, 862 final int indentSpaces, final boolean includeProcessing) 863 { 864 // Create the request variable. 865 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "DeleteRequest", 866 requestID + "Request", "new DeleteRequest", 867 ToCodeArgHelper.createString(dn, "Entry DN")); 868 869 // If there are any controls, then add them to the request. 870 for (final Control c : getControls()) 871 { 872 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 873 requestID + "Request.addControl", 874 ToCodeArgHelper.createControl(c, null)); 875 } 876 877 878 // Add lines for processing the request and obtaining the result. 879 if (includeProcessing) 880 { 881 // Generate a string with the appropriate indent. 882 final StringBuilder buffer = new StringBuilder(); 883 for (int i=0; i < indentSpaces; i++) 884 { 885 buffer.append(' '); 886 } 887 final String indent = buffer.toString(); 888 889 lineList.add(""); 890 lineList.add(indent + "try"); 891 lineList.add(indent + '{'); 892 lineList.add(indent + " LDAPResult " + requestID + 893 "Result = connection.delete(" + requestID + "Request);"); 894 lineList.add(indent + " // The delete was processed successfully."); 895 lineList.add(indent + '}'); 896 lineList.add(indent + "catch (LDAPException e)"); 897 lineList.add(indent + '{'); 898 lineList.add(indent + " // The delete failed. Maybe the following " + 899 "will help explain why."); 900 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 901 lineList.add(indent + " String message = e.getMessage();"); 902 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 903 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 904 lineList.add(indent + " Control[] responseControls = " + 905 "e.getResponseControls();"); 906 lineList.add(indent + '}'); 907 } 908 } 909}