001/* 002 * Copyright 2007-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2018 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk; 022 023 024 025import java.util.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.InternalUseOnly; 039import com.unboundid.util.Mutable; 040import com.unboundid.util.ThreadSafety; 041import com.unboundid.util.ThreadSafetyLevel; 042 043import static com.unboundid.ldap.sdk.LDAPMessages.*; 044import static com.unboundid.util.Debug.*; 045import static com.unboundid.util.StaticUtils.*; 046import static com.unboundid.util.Validator.*; 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<LDAPResponse>(); 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 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 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 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 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 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 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 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 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 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 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 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 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 = nanosToMillis(System.nanoTime() - requestTime); 520 if (connection.getConnectionOptions().abandonOnTimeout()) 521 { 522 connection.abandon(messageID); 523 } 524 525 throw new LDAPException(ResultCode.TIMEOUT, 526 ERR_DELETE_CLIENT_TIMEOUT.get(waitTime, messageID, dn, 527 connection.getHostPort())); 528 } 529 530 connection.getConnectionStatistics().incrementNumDeleteResponses( 531 System.nanoTime() - requestTime); 532 if (response instanceof ConnectionClosedResponse) 533 { 534 // The connection was closed while waiting for the response. 535 if (allowRetry) 536 { 537 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 538 ResultCode.SERVER_DOWN); 539 if (retryResult != null) 540 { 541 return retryResult; 542 } 543 } 544 545 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 546 final String message = ccr.getMessage(); 547 if (message == null) 548 { 549 throw new LDAPException(ccr.getResultCode(), 550 ERR_CONN_CLOSED_WAITING_FOR_DELETE_RESPONSE.get( 551 connection.getHostPort(), toString())); 552 } 553 else 554 { 555 throw new LDAPException(ccr.getResultCode(), 556 ERR_CONN_CLOSED_WAITING_FOR_DELETE_RESPONSE_WITH_MESSAGE.get( 557 connection.getHostPort(), toString(), message)); 558 } 559 } 560 561 final LDAPResult result = (LDAPResult) response; 562 if ((result.getResultCode().equals(ResultCode.REFERRAL)) && 563 followReferrals(connection)) 564 { 565 if (depth >= connection.getConnectionOptions().getReferralHopLimit()) 566 { 567 return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED, 568 ERR_TOO_MANY_REFERRALS.get(), 569 result.getMatchedDN(), result.getReferralURLs(), 570 result.getResponseControls()); 571 } 572 573 return followReferral(result, connection, depth); 574 } 575 else 576 { 577 if (allowRetry) 578 { 579 final LDAPResult retryResult = reconnectAndRetry(connection, depth, 580 result.getResultCode()); 581 if (retryResult != null) 582 { 583 return retryResult; 584 } 585 } 586 587 return result; 588 } 589 } 590 591 592 593 /** 594 * Attempts to re-establish the connection and retry processing this request 595 * on it. 596 * 597 * @param connection The connection to be re-established. 598 * @param depth The current referral depth for this request. It should 599 * always be one for the initial request, and should only 600 * be incremented when following referrals. 601 * @param resultCode The result code for the previous operation attempt. 602 * 603 * @return The result from re-trying the add, or {@code null} if it could not 604 * be re-tried. 605 */ 606 private LDAPResult reconnectAndRetry(final LDAPConnection connection, 607 final int depth, 608 final ResultCode resultCode) 609 { 610 try 611 { 612 // We will only want to retry for certain result codes that indicate a 613 // connection problem. 614 switch (resultCode.intValue()) 615 { 616 case ResultCode.SERVER_DOWN_INT_VALUE: 617 case ResultCode.DECODING_ERROR_INT_VALUE: 618 case ResultCode.CONNECT_ERROR_INT_VALUE: 619 connection.reconnect(); 620 return processSync(connection, depth, false); 621 } 622 } 623 catch (final Exception e) 624 { 625 debugException(e); 626 } 627 628 return null; 629 } 630 631 632 633 /** 634 * Attempts to follow a referral to perform a delete operation in the target 635 * server. 636 * 637 * @param referralResult The LDAP result object containing information about 638 * the referral to follow. 639 * @param connection The connection on which the referral was received. 640 * @param depth The number of referrals followed in the course of 641 * processing this request. 642 * 643 * @return The result of attempting to process the delete operation by 644 * following the referral. 645 * 646 * @throws LDAPException If a problem occurs while attempting to establish 647 * the referral connection, sending the request, or 648 * reading the result. 649 */ 650 private LDAPResult followReferral(final LDAPResult referralResult, 651 final LDAPConnection connection, 652 final int depth) 653 throws LDAPException 654 { 655 for (final String urlString : referralResult.getReferralURLs()) 656 { 657 try 658 { 659 final LDAPURL referralURL = new LDAPURL(urlString); 660 final String host = referralURL.getHost(); 661 662 if (host == null) 663 { 664 // We can't handle a referral in which there is no host. 665 continue; 666 } 667 668 final DeleteRequest deleteRequest; 669 if (referralURL.baseDNProvided()) 670 { 671 deleteRequest = new DeleteRequest(referralURL.getBaseDN(), 672 getControls()); 673 } 674 else 675 { 676 deleteRequest = this; 677 } 678 679 final LDAPConnection referralConn = getReferralConnector(connection). 680 getReferralConnection(referralURL, connection); 681 try 682 { 683 return deleteRequest.process(referralConn, depth+1); 684 } 685 finally 686 { 687 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null); 688 referralConn.close(); 689 } 690 } 691 catch (final LDAPException le) 692 { 693 debugException(le); 694 } 695 } 696 697 // If we've gotten here, then we could not follow any of the referral URLs, 698 // so we'll just return the original referral result. 699 return referralResult; 700 } 701 702 703 704 /** 705 * {@inheritDoc} 706 */ 707 @InternalUseOnly() 708 @Override() 709 public void responseReceived(final LDAPResponse response) 710 throws LDAPException 711 { 712 try 713 { 714 responseQueue.put(response); 715 } 716 catch (final Exception e) 717 { 718 debugException(e); 719 720 if (e instanceof InterruptedException) 721 { 722 Thread.currentThread().interrupt(); 723 } 724 725 throw new LDAPException(ResultCode.LOCAL_ERROR, 726 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); 727 } 728 } 729 730 731 732 /** 733 * {@inheritDoc} 734 */ 735 @Override() 736 public int getLastMessageID() 737 { 738 return messageID; 739 } 740 741 742 743 /** 744 * {@inheritDoc} 745 */ 746 @Override() 747 public OperationType getOperationType() 748 { 749 return OperationType.DELETE; 750 } 751 752 753 754 /** 755 * {@inheritDoc} 756 */ 757 @Override() 758 public DeleteRequest duplicate() 759 { 760 return duplicate(getControls()); 761 } 762 763 764 765 /** 766 * {@inheritDoc} 767 */ 768 @Override() 769 public DeleteRequest duplicate(final Control[] controls) 770 { 771 final DeleteRequest r = new DeleteRequest(dn, controls); 772 773 if (followReferralsInternal() != null) 774 { 775 r.setFollowReferrals(followReferralsInternal()); 776 } 777 778 if (getReferralConnectorInternal() != null) 779 { 780 r.setReferralConnector(getReferralConnectorInternal()); 781 } 782 783 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 784 785 return r; 786 } 787 788 789 790 /** 791 * {@inheritDoc} 792 */ 793 @Override() 794 public LDIFDeleteChangeRecord toLDIFChangeRecord() 795 { 796 return new LDIFDeleteChangeRecord(this); 797 } 798 799 800 801 /** 802 * {@inheritDoc} 803 */ 804 @Override() 805 public String[] toLDIF() 806 { 807 return toLDIFChangeRecord().toLDIF(); 808 } 809 810 811 812 /** 813 * {@inheritDoc} 814 */ 815 @Override() 816 public String toLDIFString() 817 { 818 return toLDIFChangeRecord().toLDIFString(); 819 } 820 821 822 823 /** 824 * {@inheritDoc} 825 */ 826 @Override() 827 public void toString(final StringBuilder buffer) 828 { 829 buffer.append("DeleteRequest(dn='"); 830 buffer.append(dn); 831 buffer.append('\''); 832 833 final Control[] controls = getControls(); 834 if (controls.length > 0) 835 { 836 buffer.append(", controls={"); 837 for (int i=0; i < controls.length; i++) 838 { 839 if (i > 0) 840 { 841 buffer.append(", "); 842 } 843 844 buffer.append(controls[i]); 845 } 846 buffer.append('}'); 847 } 848 849 buffer.append(')'); 850 } 851 852 853 854 /** 855 * {@inheritDoc} 856 */ 857 @Override() 858 public void toCode(final List<String> lineList, final String requestID, 859 final int indentSpaces, final boolean includeProcessing) 860 { 861 // Create the request variable. 862 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "DeleteRequest", 863 requestID + "Request", "new DeleteRequest", 864 ToCodeArgHelper.createString(dn, "Entry DN")); 865 866 // If there are any controls, then add them to the request. 867 for (final Control c : getControls()) 868 { 869 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 870 requestID + "Request.addControl", 871 ToCodeArgHelper.createControl(c, null)); 872 } 873 874 875 // Add lines for processing the request and obtaining the result. 876 if (includeProcessing) 877 { 878 // Generate a string with the appropriate indent. 879 final StringBuilder buffer = new StringBuilder(); 880 for (int i=0; i < indentSpaces; i++) 881 { 882 buffer.append(' '); 883 } 884 final String indent = buffer.toString(); 885 886 lineList.add(""); 887 lineList.add(indent + "try"); 888 lineList.add(indent + '{'); 889 lineList.add(indent + " LDAPResult " + requestID + 890 "Result = connection.delete(" + requestID + "Request);"); 891 lineList.add(indent + " // The delete was processed successfully."); 892 lineList.add(indent + '}'); 893 lineList.add(indent + "catch (LDAPException e)"); 894 lineList.add(indent + '{'); 895 lineList.add(indent + " // The delete failed. Maybe the following " + 896 "will help explain why."); 897 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 898 lineList.add(indent + " String message = e.getMessage();"); 899 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 900 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 901 lineList.add(indent + " Control[] responseControls = " + 902 "e.getResponseControls();"); 903 lineList.add(indent + '}'); 904 } 905 } 906}