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