001/* 002 * Copyright 2007-2026 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2026 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-2026 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 return processSync(connection, depth); 297 } 298 299 final long requestTime = System.nanoTime(); 300 processAsync(connection, null); 301 302 try 303 { 304 // Wait for and process the response. 305 final LDAPResponse response; 306 try 307 { 308 final long responseTimeout = getResponseTimeoutMillis(connection); 309 if (responseTimeout > 0) 310 { 311 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS); 312 } 313 else 314 { 315 response = responseQueue.take(); 316 } 317 } 318 catch (final InterruptedException ie) 319 { 320 Debug.debugException(ie); 321 Thread.currentThread().interrupt(); 322 throw new LDAPException(ResultCode.LOCAL_ERROR, 323 ERR_DELETE_INTERRUPTED.get(connection.getHostPort()), ie); 324 } 325 326 return handleResponse(connection, response, requestTime, depth); 327 } 328 finally 329 { 330 connection.deregisterResponseAcceptor(messageID); 331 } 332 } 333 334 335 336 /** 337 * Sends this delete request to the directory server over the provided 338 * connection and returns the message ID for the request. 339 * 340 * @param connection The connection to use to communicate with the 341 * directory server. 342 * @param resultListener The async result listener that is to be notified 343 * when the response is received. It may be 344 * {@code null} only if the result is to be processed 345 * by this class. 346 * 347 * @return The async request ID created for the operation, or {@code null} if 348 * the provided {@code resultListener} is {@code null} and the 349 * operation will not actually be processed asynchronously. 350 * 351 * @throws LDAPException If a problem occurs while sending the request. 352 */ 353 @Nullable() 354 AsyncRequestID processAsync(@NotNull final LDAPConnection connection, 355 @Nullable final AsyncResultListener resultListener) 356 throws LDAPException 357 { 358 // Create the LDAP message. 359 messageID = connection.nextMessageID(); 360 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 361 362 363 // If the provided async result listener is {@code null}, then we'll use 364 // this class as the message acceptor. Otherwise, create an async helper 365 // and use it as the message acceptor. 366 final AsyncRequestID asyncRequestID; 367 final long timeout = getResponseTimeoutMillis(connection); 368 if (resultListener == null) 369 { 370 asyncRequestID = null; 371 connection.registerResponseAcceptor(messageID, this); 372 } 373 else 374 { 375 final AsyncHelper helper = new AsyncHelper(connection, 376 OperationType.DELETE, messageID, resultListener, 377 getIntermediateResponseListener()); 378 connection.registerResponseAcceptor(messageID, helper); 379 asyncRequestID = helper.getAsyncRequestID(); 380 381 if (timeout > 0L) 382 { 383 final Timer timer = connection.getTimer(); 384 final AsyncTimeoutTimerTask timerTask = 385 new AsyncTimeoutTimerTask(helper); 386 timer.schedule(timerTask, timeout); 387 asyncRequestID.setTimerTask(timerTask); 388 } 389 } 390 391 392 // Send the request to the server. 393 try 394 { 395 Debug.debugLDAPRequest(Level.INFO, this, messageID, connection); 396 397 final LDAPConnectionLogger logger = 398 connection.getConnectionOptions().getConnectionLogger(); 399 if (logger != null) 400 { 401 logger.logDeleteRequest(connection, messageID, this); 402 } 403 404 connection.getConnectionStatistics().incrementNumDeleteRequests(); 405 connection.sendMessage(message, timeout); 406 return asyncRequestID; 407 } 408 catch (final LDAPException le) 409 { 410 Debug.debugException(le); 411 412 connection.deregisterResponseAcceptor(messageID); 413 throw le; 414 } 415 } 416 417 418 419 /** 420 * Processes this delete operation in synchronous mode, in which the same 421 * thread will send the request and read the response. 422 * 423 * @param connection The connection to use to communicate with the directory 424 * server. 425 * @param depth The current referral depth for this request. It should 426 * always be one for the initial request, and should only 427 * be incremented when following referrals. 428 * 429 * @return An LDAP result object that provides information about the result 430 * of the delete processing. 431 * 432 * @throws LDAPException If a problem occurs while sending the request or 433 * reading the response. 434 */ 435 @NotNull() 436 private LDAPResult processSync(@NotNull final LDAPConnection connection, 437 final int depth) 438 throws LDAPException 439 { 440 // Create the LDAP message. 441 messageID = connection.nextMessageID(); 442 final LDAPMessage message = 443 new LDAPMessage(messageID, this, getControls()); 444 445 446 // Send the request to the server. 447 final long requestTime = System.nanoTime(); 448 Debug.debugLDAPRequest(Level.INFO, this, messageID, connection); 449 450 final LDAPConnectionLogger logger = 451 connection.getConnectionOptions().getConnectionLogger(); 452 if (logger != null) 453 { 454 logger.logDeleteRequest(connection, messageID, this); 455 } 456 457 connection.getConnectionStatistics().incrementNumDeleteRequests(); 458 connection.sendMessage(message, getResponseTimeoutMillis(connection)); 459 460 while (true) 461 { 462 final LDAPResponse response; 463 try 464 { 465 response = connection.readResponse(messageID); 466 } 467 catch (final LDAPException le) 468 { 469 Debug.debugException(le); 470 471 if ((le.getResultCode() == ResultCode.TIMEOUT) && 472 connection.getConnectionOptions().abandonOnTimeout()) 473 { 474 connection.abandon(messageID); 475 } 476 477 throw le; 478 } 479 480 if (response instanceof IntermediateResponse) 481 { 482 final IntermediateResponseListener listener = 483 getIntermediateResponseListener(); 484 if (listener != null) 485 { 486 listener.intermediateResponseReturned( 487 (IntermediateResponse) response); 488 } 489 } 490 else 491 { 492 return handleResponse(connection, response, requestTime, depth); 493 } 494 } 495 } 496 497 498 499 /** 500 * Performs the necessary processing for handling a response. 501 * 502 * @param connection The connection used to read the response. 503 * @param response The response to be processed. 504 * @param requestTime The time the request was sent to the server. 505 * @param depth The current referral depth for this request. It 506 * should always be one for the initial request, and 507 * should only be incremented when following referrals. 508 * 509 * @return The delete result. 510 * 511 * @throws LDAPException If a problem occurs. 512 */ 513 @NotNull() 514 private LDAPResult handleResponse(@NotNull final LDAPConnection connection, 515 @Nullable final LDAPResponse response, 516 final long requestTime, final int depth) 517 throws LDAPException 518 { 519 if (response == null) 520 { 521 final long waitTime = 522 StaticUtils.nanosToMillis(System.nanoTime() - requestTime); 523 if (connection.getConnectionOptions().abandonOnTimeout()) 524 { 525 connection.abandon(messageID); 526 } 527 528 throw new LDAPException(ResultCode.TIMEOUT, 529 ERR_DELETE_CLIENT_TIMEOUT.get(waitTime, messageID, dn, 530 connection.getHostPort())); 531 } 532 533 connection.getConnectionStatistics().incrementNumDeleteResponses( 534 System.nanoTime() - requestTime); 535 if (response instanceof ConnectionClosedResponse) 536 { 537 // The connection was closed while waiting for the response. 538 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 539 final String message = ccr.getMessage(); 540 if (message == null) 541 { 542 throw new LDAPException(ccr.getResultCode(), 543 ERR_CONN_CLOSED_WAITING_FOR_DELETE_RESPONSE.get( 544 connection.getHostPort(), toString())); 545 } 546 else 547 { 548 throw new LDAPException(ccr.getResultCode(), 549 ERR_CONN_CLOSED_WAITING_FOR_DELETE_RESPONSE_WITH_MESSAGE.get( 550 connection.getHostPort(), toString(), message)); 551 } 552 } 553 554 final LDAPResult result = (LDAPResult) response; 555 if ((result.getResultCode().equals(ResultCode.REFERRAL)) && 556 followReferrals(connection)) 557 { 558 if (depth >= connection.getConnectionOptions().getReferralHopLimit()) 559 { 560 return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED, 561 ERR_TOO_MANY_REFERRALS.get(), 562 result.getMatchedDN(), result.getReferralURLs(), 563 result.getResponseControls()); 564 } 565 566 return ReferralHelper.handleReferral(this, result, connection); 567 } 568 else 569 { 570 return result; 571 } 572 } 573 574 575 576 /** 577 * {@inheritDoc} 578 */ 579 @InternalUseOnly() 580 @Override() 581 public void responseReceived(@NotNull final LDAPResponse response) 582 throws LDAPException 583 { 584 try 585 { 586 responseQueue.put(response); 587 } 588 catch (final Exception e) 589 { 590 Debug.debugException(e); 591 592 if (e instanceof InterruptedException) 593 { 594 Thread.currentThread().interrupt(); 595 } 596 597 throw new LDAPException(ResultCode.LOCAL_ERROR, 598 ERR_EXCEPTION_HANDLING_RESPONSE.get( 599 StaticUtils.getExceptionMessage(e)), 600 e); 601 } 602 } 603 604 605 606 /** 607 * {@inheritDoc} 608 */ 609 @Override() 610 public int getLastMessageID() 611 { 612 return messageID; 613 } 614 615 616 617 /** 618 * {@inheritDoc} 619 */ 620 @Override() 621 @NotNull() 622 public OperationType getOperationType() 623 { 624 return OperationType.DELETE; 625 } 626 627 628 629 /** 630 * {@inheritDoc} 631 */ 632 @Override() 633 @NotNull() 634 public DeleteRequest duplicate() 635 { 636 return duplicate(getControls()); 637 } 638 639 640 641 /** 642 * {@inheritDoc} 643 */ 644 @Override() 645 @NotNull() 646 public DeleteRequest duplicate(@Nullable final Control[] controls) 647 { 648 final DeleteRequest r = new DeleteRequest(dn, controls); 649 650 if (followReferralsInternal() != null) 651 { 652 r.setFollowReferrals(followReferralsInternal()); 653 } 654 655 if (getReferralConnectorInternal() != null) 656 { 657 r.setReferralConnector(getReferralConnectorInternal()); 658 } 659 660 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 661 r.setIntermediateResponseListener(getIntermediateResponseListener()); 662 r.setReferralDepth(getReferralDepth()); 663 r.setReferralConnector(getReferralConnectorInternal()); 664 665 return r; 666 } 667 668 669 670 /** 671 * {@inheritDoc} 672 */ 673 @Override() 674 @NotNull() 675 public LDIFDeleteChangeRecord toLDIFChangeRecord() 676 { 677 return new LDIFDeleteChangeRecord(this); 678 } 679 680 681 682 /** 683 * {@inheritDoc} 684 */ 685 @Override() 686 @NotNull() 687 public String[] toLDIF() 688 { 689 return toLDIFChangeRecord().toLDIF(); 690 } 691 692 693 694 /** 695 * {@inheritDoc} 696 */ 697 @Override() 698 @NotNull() 699 public String toLDIFString() 700 { 701 return toLDIFChangeRecord().toLDIFString(); 702 } 703 704 705 706 /** 707 * {@inheritDoc} 708 */ 709 @Override() 710 public void toString(@NotNull final StringBuilder buffer) 711 { 712 buffer.append("DeleteRequest(dn='"); 713 buffer.append(dn); 714 buffer.append('\''); 715 716 final Control[] controls = getControls(); 717 if (controls.length > 0) 718 { 719 buffer.append(", controls={"); 720 for (int i=0; i < controls.length; i++) 721 { 722 if (i > 0) 723 { 724 buffer.append(", "); 725 } 726 727 buffer.append(controls[i]); 728 } 729 buffer.append('}'); 730 } 731 732 buffer.append(')'); 733 } 734 735 736 737 /** 738 * {@inheritDoc} 739 */ 740 @Override() 741 public void toCode(@NotNull final List<String> lineList, 742 @NotNull final String requestID, 743 final int indentSpaces, final boolean includeProcessing) 744 { 745 // Create the request variable. 746 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "DeleteRequest", 747 requestID + "Request", "new DeleteRequest", 748 ToCodeArgHelper.createString(dn, "Entry DN")); 749 750 // If there are any controls, then add them to the request. 751 for (final Control c : getControls()) 752 { 753 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 754 requestID + "Request.addControl", 755 ToCodeArgHelper.createControl(c, null)); 756 } 757 758 759 // Add lines for processing the request and obtaining the result. 760 if (includeProcessing) 761 { 762 // Generate a string with the appropriate indent. 763 final StringBuilder buffer = new StringBuilder(); 764 for (int i=0; i < indentSpaces; i++) 765 { 766 buffer.append(' '); 767 } 768 final String indent = buffer.toString(); 769 770 lineList.add(""); 771 lineList.add(indent + "try"); 772 lineList.add(indent + '{'); 773 lineList.add(indent + " LDAPResult " + requestID + 774 "Result = connection.delete(" + requestID + "Request);"); 775 lineList.add(indent + " // The delete was processed successfully."); 776 lineList.add(indent + '}'); 777 lineList.add(indent + "catch (LDAPException e)"); 778 lineList.add(indent + '{'); 779 lineList.add(indent + " // The delete failed. Maybe the following " + 780 "will help explain why."); 781 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 782 lineList.add(indent + " String message = e.getMessage();"); 783 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 784 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 785 lineList.add(indent + " Control[] responseControls = " + 786 "e.getResponseControls();"); 787 lineList.add(indent + '}'); 788 } 789 } 790}