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