001/*
002 * Copyright 2007-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2022 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-2022 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    if (connection.synchronousMode())
293    {
294      @SuppressWarnings("deprecation")
295      final boolean autoReconnect =
296           connection.getConnectionOptions().autoReconnect();
297      return processSync(connection, depth, autoReconnect);
298    }
299
300    final long requestTime = System.nanoTime();
301    processAsync(connection, null);
302
303    try
304    {
305      // Wait for and process the response.
306      final LDAPResponse response;
307      try
308      {
309        final long responseTimeout = getResponseTimeoutMillis(connection);
310        if (responseTimeout > 0)
311        {
312          response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS);
313        }
314        else
315        {
316          response = responseQueue.take();
317        }
318      }
319      catch (final InterruptedException ie)
320      {
321        Debug.debugException(ie);
322        Thread.currentThread().interrupt();
323        throw new LDAPException(ResultCode.LOCAL_ERROR,
324             ERR_DELETE_INTERRUPTED.get(connection.getHostPort()), ie);
325      }
326
327      return handleResponse(connection, response,  requestTime, depth, false);
328    }
329    finally
330    {
331      connection.deregisterResponseAcceptor(messageID);
332    }
333  }
334
335
336
337  /**
338   * Sends this delete request to the directory server over the provided
339   * connection and returns the message ID for the request.
340   *
341   * @param  connection      The connection to use to communicate with the
342   *                         directory server.
343   * @param  resultListener  The async result listener that is to be notified
344   *                         when the response is received.  It may be
345   *                         {@code null} only if the result is to be processed
346   *                         by this class.
347   *
348   * @return  The async request ID created for the operation, or {@code null} if
349   *          the provided {@code resultListener} is {@code null} and the
350   *          operation will not actually be processed asynchronously.
351   *
352   * @throws  LDAPException  If a problem occurs while sending the request.
353   */
354  @Nullable()
355  AsyncRequestID processAsync(@NotNull final LDAPConnection connection,
356                      @Nullable final AsyncResultListener resultListener)
357                 throws LDAPException
358  {
359    // Create the LDAP message.
360    messageID = connection.nextMessageID();
361    final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
362
363
364    // If the provided async result listener is {@code null}, then we'll use
365    // this class as the message acceptor.  Otherwise, create an async helper
366    // and use it as the message acceptor.
367    final AsyncRequestID asyncRequestID;
368    final long timeout = getResponseTimeoutMillis(connection);
369    if (resultListener == null)
370    {
371      asyncRequestID = null;
372      connection.registerResponseAcceptor(messageID, this);
373    }
374    else
375    {
376      final AsyncHelper helper = new AsyncHelper(connection,
377           OperationType.DELETE, messageID, resultListener,
378           getIntermediateResponseListener());
379      connection.registerResponseAcceptor(messageID, helper);
380      asyncRequestID = helper.getAsyncRequestID();
381
382      if (timeout > 0L)
383      {
384        final Timer timer = connection.getTimer();
385        final AsyncTimeoutTimerTask timerTask =
386             new AsyncTimeoutTimerTask(helper);
387        timer.schedule(timerTask, timeout);
388        asyncRequestID.setTimerTask(timerTask);
389      }
390    }
391
392
393    // Send the request to the server.
394    try
395    {
396      Debug.debugLDAPRequest(Level.INFO, this, messageID, connection);
397
398      final LDAPConnectionLogger logger =
399           connection.getConnectionOptions().getConnectionLogger();
400      if (logger != null)
401      {
402        logger.logDeleteRequest(connection, messageID, this);
403      }
404
405      connection.getConnectionStatistics().incrementNumDeleteRequests();
406      connection.sendMessage(message, timeout);
407      return asyncRequestID;
408    }
409    catch (final LDAPException le)
410    {
411      Debug.debugException(le);
412
413      connection.deregisterResponseAcceptor(messageID);
414      throw le;
415    }
416  }
417
418
419
420  /**
421   * Processes this delete operation in synchronous mode, in which the same
422   * thread will send the request and read the response.
423   *
424   * @param  connection  The connection to use to communicate with the directory
425   *                     server.
426   * @param  depth       The current referral depth for this request.  It should
427   *                     always be one for the initial request, and should only
428   *                     be incremented when following referrals.
429   * @param  allowRetry  Indicates whether the request may be re-tried on a
430   *                     re-established connection if the initial attempt fails
431   *                     in a way that indicates the connection is no longer
432   *                     valid and autoReconnect is true.
433   *
434   * @return  An LDAP result object that provides information about the result
435   *          of the delete processing.
436   *
437   * @throws  LDAPException  If a problem occurs while sending the request or
438   *                         reading the response.
439   */
440  @NotNull()
441  private LDAPResult processSync(@NotNull final LDAPConnection connection,
442                                 final int depth, final boolean allowRetry)
443          throws LDAPException
444  {
445    // Create the LDAP message.
446    messageID = connection.nextMessageID();
447    final LDAPMessage message =
448         new LDAPMessage(messageID,  this, getControls());
449
450
451    // Send the request to the server.
452    final long requestTime = System.nanoTime();
453    Debug.debugLDAPRequest(Level.INFO, this, messageID, connection);
454
455    final LDAPConnectionLogger logger =
456         connection.getConnectionOptions().getConnectionLogger();
457    if (logger != null)
458    {
459      logger.logDeleteRequest(connection, messageID, this);
460    }
461
462    connection.getConnectionStatistics().incrementNumDeleteRequests();
463    try
464    {
465      connection.sendMessage(message, getResponseTimeoutMillis(connection));
466    }
467    catch (final LDAPException le)
468    {
469      Debug.debugException(le);
470
471      if (allowRetry)
472      {
473        final LDAPResult retryResult = reconnectAndRetry(connection, depth,
474             le.getResultCode());
475        if (retryResult != null)
476        {
477          return retryResult;
478        }
479      }
480
481      throw le;
482    }
483
484    while (true)
485    {
486      final LDAPResponse response;
487      try
488      {
489        response = connection.readResponse(messageID);
490      }
491      catch (final LDAPException le)
492      {
493        Debug.debugException(le);
494
495        if ((le.getResultCode() == ResultCode.TIMEOUT) &&
496            connection.getConnectionOptions().abandonOnTimeout())
497        {
498          connection.abandon(messageID);
499        }
500
501        if (allowRetry)
502        {
503          final LDAPResult retryResult = reconnectAndRetry(connection, depth,
504               le.getResultCode());
505          if (retryResult != null)
506          {
507            return retryResult;
508          }
509        }
510
511        throw le;
512      }
513
514      if (response instanceof IntermediateResponse)
515      {
516        final IntermediateResponseListener listener =
517             getIntermediateResponseListener();
518        if (listener != null)
519        {
520          listener.intermediateResponseReturned(
521               (IntermediateResponse) response);
522        }
523      }
524      else
525      {
526        return handleResponse(connection, response, requestTime, depth,
527             allowRetry);
528      }
529    }
530  }
531
532
533
534  /**
535   * Performs the necessary processing for handling a response.
536   *
537   * @param  connection   The connection used to read the response.
538   * @param  response     The response to be processed.
539   * @param  requestTime  The time the request was sent to the server.
540   * @param  depth        The current referral depth for this request.  It
541   *                      should always be one for the initial request, and
542   *                      should only be incremented when following referrals.
543   * @param  allowRetry   Indicates whether the request may be re-tried on a
544   *                      re-established connection if the initial attempt fails
545   *                      in a way that indicates the connection is no longer
546   *                      valid and autoReconnect is true.
547   *
548   * @return  The delete result.
549   *
550   * @throws  LDAPException  If a problem occurs.
551   */
552  @NotNull()
553  private LDAPResult handleResponse(@NotNull final LDAPConnection connection,
554                                    @Nullable final LDAPResponse response,
555                                    final long requestTime, final int depth,
556                                    final boolean allowRetry)
557          throws LDAPException
558  {
559    if (response == null)
560    {
561      final long waitTime =
562           StaticUtils.nanosToMillis(System.nanoTime() - requestTime);
563      if (connection.getConnectionOptions().abandonOnTimeout())
564      {
565        connection.abandon(messageID);
566      }
567
568      throw new LDAPException(ResultCode.TIMEOUT,
569           ERR_DELETE_CLIENT_TIMEOUT.get(waitTime, messageID, dn,
570                connection.getHostPort()));
571    }
572
573    connection.getConnectionStatistics().incrementNumDeleteResponses(
574         System.nanoTime() - requestTime);
575    if (response instanceof ConnectionClosedResponse)
576    {
577      // The connection was closed while waiting for the response.
578      if (allowRetry)
579      {
580        final LDAPResult retryResult = reconnectAndRetry(connection, depth,
581             ResultCode.SERVER_DOWN);
582        if (retryResult != null)
583        {
584          return retryResult;
585        }
586      }
587
588      final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
589      final String message = ccr.getMessage();
590      if (message == null)
591      {
592        throw new LDAPException(ccr.getResultCode(),
593             ERR_CONN_CLOSED_WAITING_FOR_DELETE_RESPONSE.get(
594                  connection.getHostPort(), toString()));
595      }
596      else
597      {
598        throw new LDAPException(ccr.getResultCode(),
599             ERR_CONN_CLOSED_WAITING_FOR_DELETE_RESPONSE_WITH_MESSAGE.get(
600                  connection.getHostPort(), toString(), message));
601      }
602    }
603
604    final LDAPResult result = (LDAPResult) response;
605    if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
606        followReferrals(connection))
607    {
608      if (depth >= connection.getConnectionOptions().getReferralHopLimit())
609      {
610        return new LDAPResult(messageID, ResultCode.REFERRAL_LIMIT_EXCEEDED,
611                              ERR_TOO_MANY_REFERRALS.get(),
612                              result.getMatchedDN(), result.getReferralURLs(),
613                              result.getResponseControls());
614      }
615
616      return followReferral(result, connection, depth);
617    }
618    else
619    {
620      if (allowRetry)
621      {
622        final LDAPResult retryResult = reconnectAndRetry(connection, depth,
623             result.getResultCode());
624        if (retryResult != null)
625        {
626          return retryResult;
627        }
628      }
629
630      return result;
631    }
632  }
633
634
635
636  /**
637   * Attempts to re-establish the connection and retry processing this request
638   * on it.
639   *
640   * @param  connection  The connection to be re-established.
641   * @param  depth       The current referral depth for this request.  It should
642   *                     always be one for the initial request, and should only
643   *                     be incremented when following referrals.
644   * @param  resultCode  The result code for the previous operation attempt.
645   *
646   * @return  The result from re-trying the add, or {@code null} if it could not
647   *          be re-tried.
648   */
649  @Nullable()
650  private LDAPResult reconnectAndRetry(@NotNull final LDAPConnection connection,
651                                       final int depth,
652                                       @NotNull final ResultCode resultCode)
653  {
654    try
655    {
656      // We will only want to retry for certain result codes that indicate a
657      // connection problem.
658      switch (resultCode.intValue())
659      {
660        case ResultCode.SERVER_DOWN_INT_VALUE:
661        case ResultCode.DECODING_ERROR_INT_VALUE:
662        case ResultCode.CONNECT_ERROR_INT_VALUE:
663          connection.reconnect();
664          return processSync(connection, depth, false);
665      }
666    }
667    catch (final Exception e)
668    {
669      Debug.debugException(e);
670    }
671
672    return null;
673  }
674
675
676
677  /**
678   * Attempts to follow a referral to perform a delete operation in the target
679   * server.
680   *
681   * @param  referralResult  The LDAP result object containing information about
682   *                         the referral to follow.
683   * @param  connection      The connection on which the referral was received.
684   * @param  depth           The number of referrals followed in the course of
685   *                         processing this request.
686   *
687   * @return  The result of attempting to process the delete operation by
688   *          following the referral.
689   *
690   * @throws  LDAPException  If a problem occurs while attempting to establish
691   *                         the referral connection, sending the request, or
692   *                         reading the result.
693   */
694  @NotNull()
695  private LDAPResult followReferral(@NotNull final LDAPResult referralResult,
696                                    @NotNull final LDAPConnection connection,
697                                    final int depth)
698          throws LDAPException
699  {
700    for (final String urlString : referralResult.getReferralURLs())
701    {
702      try
703      {
704        final LDAPURL referralURL = new LDAPURL(urlString);
705        final String host = referralURL.getHost();
706
707        if (host == null)
708        {
709          // We can't handle a referral in which there is no host.
710          continue;
711        }
712
713        final DeleteRequest deleteRequest;
714        if (referralURL.baseDNProvided())
715        {
716          deleteRequest = new DeleteRequest(referralURL.getBaseDN(),
717                                            getControls());
718        }
719        else
720        {
721          deleteRequest = this;
722        }
723
724        final LDAPConnection referralConn = getReferralConnector(connection).
725             getReferralConnection(referralURL, connection);
726        try
727        {
728          return deleteRequest.process(referralConn, depth+1);
729        }
730        finally
731        {
732          referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
733          referralConn.close();
734        }
735      }
736      catch (final LDAPException le)
737      {
738        Debug.debugException(le);
739      }
740    }
741
742    // If we've gotten here, then we could not follow any of the referral URLs,
743    // so we'll just return the original referral result.
744    return referralResult;
745  }
746
747
748
749  /**
750   * {@inheritDoc}
751   */
752  @InternalUseOnly()
753  @Override()
754  public void responseReceived(@NotNull final LDAPResponse response)
755         throws LDAPException
756  {
757    try
758    {
759      responseQueue.put(response);
760    }
761    catch (final Exception e)
762    {
763      Debug.debugException(e);
764
765      if (e instanceof InterruptedException)
766      {
767        Thread.currentThread().interrupt();
768      }
769
770      throw new LDAPException(ResultCode.LOCAL_ERROR,
771           ERR_EXCEPTION_HANDLING_RESPONSE.get(
772                StaticUtils.getExceptionMessage(e)),
773           e);
774    }
775  }
776
777
778
779  /**
780   * {@inheritDoc}
781   */
782  @Override()
783  public int getLastMessageID()
784  {
785    return messageID;
786  }
787
788
789
790  /**
791   * {@inheritDoc}
792   */
793  @Override()
794  @NotNull()
795  public OperationType getOperationType()
796  {
797    return OperationType.DELETE;
798  }
799
800
801
802  /**
803   * {@inheritDoc}
804   */
805  @Override()
806  @NotNull()
807  public DeleteRequest duplicate()
808  {
809    return duplicate(getControls());
810  }
811
812
813
814  /**
815   * {@inheritDoc}
816   */
817  @Override()
818  @NotNull()
819  public DeleteRequest duplicate(@Nullable final Control[] controls)
820  {
821    final DeleteRequest r = new DeleteRequest(dn, controls);
822
823    if (followReferralsInternal() != null)
824    {
825      r.setFollowReferrals(followReferralsInternal());
826    }
827
828    if (getReferralConnectorInternal() != null)
829    {
830      r.setReferralConnector(getReferralConnectorInternal());
831    }
832
833    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
834
835    return r;
836  }
837
838
839
840  /**
841   * {@inheritDoc}
842   */
843  @Override()
844  @NotNull()
845  public LDIFDeleteChangeRecord toLDIFChangeRecord()
846  {
847    return new LDIFDeleteChangeRecord(this);
848  }
849
850
851
852  /**
853   * {@inheritDoc}
854   */
855  @Override()
856  @NotNull()
857  public String[] toLDIF()
858  {
859    return toLDIFChangeRecord().toLDIF();
860  }
861
862
863
864  /**
865   * {@inheritDoc}
866   */
867  @Override()
868  @NotNull()
869  public String toLDIFString()
870  {
871    return toLDIFChangeRecord().toLDIFString();
872  }
873
874
875
876  /**
877   * {@inheritDoc}
878   */
879  @Override()
880  public void toString(@NotNull final StringBuilder buffer)
881  {
882    buffer.append("DeleteRequest(dn='");
883    buffer.append(dn);
884    buffer.append('\'');
885
886    final Control[] controls = getControls();
887    if (controls.length > 0)
888    {
889      buffer.append(", controls={");
890      for (int i=0; i < controls.length; i++)
891      {
892        if (i > 0)
893        {
894          buffer.append(", ");
895        }
896
897        buffer.append(controls[i]);
898      }
899      buffer.append('}');
900    }
901
902    buffer.append(')');
903  }
904
905
906
907  /**
908   * {@inheritDoc}
909   */
910  @Override()
911  public void toCode(@NotNull final List<String> lineList,
912                     @NotNull final String requestID,
913                     final int indentSpaces, final boolean includeProcessing)
914  {
915    // Create the request variable.
916    ToCodeHelper.generateMethodCall(lineList, indentSpaces, "DeleteRequest",
917         requestID + "Request", "new DeleteRequest",
918         ToCodeArgHelper.createString(dn, "Entry DN"));
919
920    // If there are any controls, then add them to the request.
921    for (final Control c : getControls())
922    {
923      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
924           requestID + "Request.addControl",
925           ToCodeArgHelper.createControl(c, null));
926    }
927
928
929    // Add lines for processing the request and obtaining the result.
930    if (includeProcessing)
931    {
932      // Generate a string with the appropriate indent.
933      final StringBuilder buffer = new StringBuilder();
934      for (int i=0; i < indentSpaces; i++)
935      {
936        buffer.append(' ');
937      }
938      final String indent = buffer.toString();
939
940      lineList.add("");
941      lineList.add(indent + "try");
942      lineList.add(indent + '{');
943      lineList.add(indent + "  LDAPResult " + requestID +
944           "Result = connection.delete(" + requestID + "Request);");
945      lineList.add(indent + "  // The delete was processed successfully.");
946      lineList.add(indent + '}');
947      lineList.add(indent + "catch (LDAPException e)");
948      lineList.add(indent + '{');
949      lineList.add(indent + "  // The delete failed.  Maybe the following " +
950           "will help explain why.");
951      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
952      lineList.add(indent + "  String message = e.getMessage();");
953      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
954      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
955      lineList.add(indent + "  Control[] responseControls = " +
956           "e.getResponseControls();");
957      lineList.add(indent + '}');
958    }
959  }
960}