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 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.util.List;
026import java.util.Timer;
027import java.util.concurrent.LinkedBlockingQueue;
028import java.util.concurrent.TimeUnit;
029
030import com.unboundid.asn1.ASN1Buffer;
031import com.unboundid.asn1.ASN1Element;
032import com.unboundid.asn1.ASN1OctetString;
033import com.unboundid.ldap.protocol.LDAPMessage;
034import com.unboundid.ldap.protocol.LDAPResponse;
035import com.unboundid.ldap.protocol.ProtocolOp;
036import com.unboundid.ldif.LDIFDeleteChangeRecord;
037import com.unboundid.util.InternalUseOnly;
038import com.unboundid.util.Mutable;
039import com.unboundid.util.ThreadSafety;
040import com.unboundid.util.ThreadSafetyLevel;
041
042import static com.unboundid.ldap.sdk.LDAPMessages.*;
043import static com.unboundid.util.Debug.*;
044import static com.unboundid.util.StaticUtils.*;
045import 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)
083public 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}