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