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}