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