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