001/*
002 * Copyright 2010-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2010-2014 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.listener;
022
023
024
025import java.io.IOException;
026import java.io.OutputStream;
027import java.net.Socket;
028import java.util.ArrayList;
029import java.util.List;
030import java.util.concurrent.CopyOnWriteArrayList;
031import java.util.concurrent.atomic.AtomicBoolean;
032import javax.net.ssl.SSLSocket;
033import javax.net.ssl.SSLSocketFactory;
034
035import com.unboundid.asn1.ASN1Buffer;
036import com.unboundid.asn1.ASN1StreamReader;
037import com.unboundid.ldap.protocol.AddResponseProtocolOp;
038import com.unboundid.ldap.protocol.BindResponseProtocolOp;
039import com.unboundid.ldap.protocol.CompareResponseProtocolOp;
040import com.unboundid.ldap.protocol.DeleteResponseProtocolOp;
041import com.unboundid.ldap.protocol.ExtendedResponseProtocolOp;
042import com.unboundid.ldap.protocol.IntermediateResponseProtocolOp;
043import com.unboundid.ldap.protocol.LDAPMessage;
044import com.unboundid.ldap.protocol.ModifyResponseProtocolOp;
045import com.unboundid.ldap.protocol.ModifyDNResponseProtocolOp;
046import com.unboundid.ldap.protocol.SearchResultDoneProtocolOp;
047import com.unboundid.ldap.protocol.SearchResultEntryProtocolOp;
048import com.unboundid.ldap.protocol.SearchResultReferenceProtocolOp;
049import com.unboundid.ldap.sdk.Attribute;
050import com.unboundid.ldap.sdk.Control;
051import com.unboundid.ldap.sdk.Entry;
052import com.unboundid.ldap.sdk.ExtendedResult;
053import com.unboundid.ldap.sdk.LDAPException;
054import com.unboundid.ldap.sdk.LDAPRuntimeException;
055import com.unboundid.ldap.sdk.ResultCode;
056import com.unboundid.util.Debug;
057import com.unboundid.util.InternalUseOnly;
058import com.unboundid.util.ObjectPair;
059import com.unboundid.util.StaticUtils;
060import com.unboundid.util.ThreadSafety;
061import com.unboundid.util.ThreadSafetyLevel;
062import com.unboundid.util.Validator;
063
064import static com.unboundid.ldap.listener.ListenerMessages.*;
065
066
067
068/**
069 * This class provides an object which will be used to represent a connection to
070 * a client accepted by an {@link LDAPListener}, although connections may also
071 * be created independently if they were accepted in some other way.  Each
072 * connection has its own thread that will be used to read requests from the
073 * client, and connections created outside of an {@code LDAPListener} instance,
074 * then the thread must be explicitly started.
075 */
076@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
077public final class LDAPListenerClientConnection
078       extends Thread
079{
080  /**
081   * A pre-allocated empty array of controls.
082   */
083  private static final Control[] EMPTY_CONTROL_ARRAY = new Control[0];
084
085
086
087  // The buffer used to hold responses to be sent to the client.
088  private final ASN1Buffer asn1Buffer;
089
090  // The ASN.1 stream reader used to read requests from the client.
091  private volatile ASN1StreamReader asn1Reader;
092
093  // Indicates whether to suppress the next call to sendMessage to send a
094  // response to the client.
095  private final AtomicBoolean suppressNextResponse;
096
097  // The set of intermediate response transformers for this connection.
098  private final CopyOnWriteArrayList<IntermediateResponseTransformer>
099       intermediateResponseTransformers;
100
101  // The set of search result entry transformers for this connection.
102  private final CopyOnWriteArrayList<SearchEntryTransformer>
103       searchEntryTransformers;
104
105  // The set of search result reference transformers for this connection.
106  private final CopyOnWriteArrayList<SearchReferenceTransformer>
107       searchReferenceTransformers;
108
109  // The listener that accepted this connection.
110  private final LDAPListener listener;
111
112  // The exception handler to use for this connection, if any.
113  private final LDAPListenerExceptionHandler exceptionHandler;
114
115  // The request handler to use for this connection.
116  private final LDAPListenerRequestHandler requestHandler;
117
118  // The connection ID assigned to this connection.
119  private final long connectionID;
120
121  // The output stream used to write responses to the client.
122  private volatile OutputStream outputStream;
123
124  // The socket used to communicate with the client.
125  private volatile Socket socket;
126
127
128
129  /**
130   * Creates a new LDAP listener client connection that will communicate with
131   * the client using the provided socket.  The {@link #start} method must be
132   * called to start listening for requests from the client.
133   *
134   * @param  listener          The listener that accepted this client
135   *                           connection.  It may be {@code null} if this
136   *                           connection was not accepted by a listener.
137   * @param  socket            The socket that may be used to communicate with
138   *                           the client.  It must not be {@code null}.
139   * @param  requestHandler    The request handler that will be used to process
140   *                           requests read from the client.  The
141   *                           {@link LDAPListenerRequestHandler#newInstance}
142   *                           method will be called on the provided object to
143   *                           obtain a new instance to use for this connection.
144   *                           The provided request handler must not be
145   *                           {@code null}.
146   * @param  exceptionHandler  The disconnect handler to be notified when this
147   *                           connection is closed.  It may be {@code null} if
148   *                           no disconnect handler should be used.
149   *
150   * @throws  LDAPException  If a problem occurs while preparing this client
151   *                         connection. for use.  If this is thrown, then the
152   *                         provided socket will be closed.
153   */
154  public LDAPListenerClientConnection(final LDAPListener listener,
155              final Socket socket,
156              final LDAPListenerRequestHandler requestHandler,
157              final LDAPListenerExceptionHandler exceptionHandler)
158         throws LDAPException
159  {
160    Validator.ensureNotNull(socket, requestHandler);
161
162    setName("LDAPListener client connection reader for connection from " +
163         socket.getInetAddress().getHostAddress() + ':' +
164         socket.getPort() + " to " + socket.getLocalAddress().getHostAddress() +
165         ':' + socket.getLocalPort());
166
167    this.listener         = listener;
168    this.socket           = socket;
169    this.exceptionHandler = exceptionHandler;
170
171    intermediateResponseTransformers =
172         new CopyOnWriteArrayList<IntermediateResponseTransformer>();
173    searchEntryTransformers =
174         new CopyOnWriteArrayList<SearchEntryTransformer>();
175    searchReferenceTransformers =
176         new CopyOnWriteArrayList<SearchReferenceTransformer>();
177
178    if (listener == null)
179    {
180      connectionID = -1L;
181    }
182    else
183    {
184      connectionID = listener.nextConnectionID();
185    }
186
187    try
188    {
189      final LDAPListenerConfig config;
190      if (listener == null)
191      {
192        config = new LDAPListenerConfig(0, requestHandler);
193      }
194      else
195      {
196        config = listener.getConfig();
197      }
198
199      socket.setKeepAlive(config.useKeepAlive());
200      socket.setReuseAddress(config.useReuseAddress());
201      socket.setSoLinger(config.useLinger(), config.getLingerTimeoutSeconds());
202      socket.setTcpNoDelay(config.useTCPNoDelay());
203
204      final int sendBufferSize = config.getSendBufferSize();
205      if (sendBufferSize > 0)
206      {
207        socket.setSendBufferSize(sendBufferSize);
208      }
209
210      asn1Reader = new ASN1StreamReader(socket.getInputStream());
211    }
212    catch (final IOException ioe)
213    {
214      Debug.debugException(ioe);
215
216      try
217      {
218        socket.close();
219      }
220      catch (final Exception e)
221      {
222        Debug.debugException(e);
223      }
224
225      throw new LDAPException(ResultCode.CONNECT_ERROR,
226           ERR_CONN_CREATE_IO_EXCEPTION.get(
227                StaticUtils.getExceptionMessage(ioe)),
228           ioe);
229    }
230
231    try
232    {
233      outputStream = socket.getOutputStream();
234    }
235    catch (final IOException ioe)
236    {
237      Debug.debugException(ioe);
238
239      try
240      {
241        asn1Reader.close();
242      }
243      catch (final Exception e)
244      {
245        Debug.debugException(e);
246      }
247
248      try
249      {
250        socket.close();
251      }
252      catch (final Exception e)
253      {
254        Debug.debugException(e);
255      }
256
257      throw new LDAPException(ResultCode.CONNECT_ERROR,
258           ERR_CONN_CREATE_IO_EXCEPTION.get(
259                StaticUtils.getExceptionMessage(ioe)),
260           ioe);
261    }
262
263    try
264    {
265      this.requestHandler = requestHandler.newInstance(this);
266    }
267    catch (final LDAPException le)
268    {
269      Debug.debugException(le);
270
271      try
272      {
273        asn1Reader.close();
274      }
275      catch (final Exception e)
276      {
277        Debug.debugException(e);
278      }
279
280      try
281      {
282        outputStream.close();
283      }
284      catch (final Exception e)
285      {
286        Debug.debugException(e);
287      }
288
289      try
290      {
291        socket.close();
292      }
293      catch (final Exception e)
294      {
295        Debug.debugException(e);
296      }
297
298      throw le;
299    }
300
301    asn1Buffer           = new ASN1Buffer();
302    suppressNextResponse = new AtomicBoolean(false);
303  }
304
305
306
307  /**
308   * Closes the connection to the client.
309   *
310   * @throws  IOException  If a problem occurs while closing the socket.
311   */
312  public synchronized void close()
313         throws IOException
314  {
315    try
316    {
317      requestHandler.closeInstance();
318    }
319    catch (final Exception e)
320    {
321      Debug.debugException(e);
322    }
323
324    try
325    {
326      asn1Reader.close();
327    }
328    catch (final Exception e)
329    {
330      Debug.debugException(e);
331    }
332
333    try
334    {
335      outputStream.close();
336    }
337    catch (final Exception e)
338    {
339      Debug.debugException(e);
340    }
341
342    socket.close();
343  }
344
345
346
347  /**
348   * Closes the connection to the client as a result of an exception encountered
349   * during processing.  Any associated exception handler will be notified
350   * prior to the connection closure.
351   *
352   * @param  le  The exception providing information about the reason that this
353   *             connection will be terminated.
354   */
355  void close(final LDAPException le)
356  {
357    if (exceptionHandler == null)
358    {
359      Debug.debugException(le);
360    }
361    else
362    {
363      try
364      {
365        exceptionHandler.connectionTerminated(this, le);
366      }
367      catch (final Exception e)
368      {
369        Debug.debugException(e);
370      }
371    }
372
373    try
374    {
375      close();
376    }
377    catch (final Exception e)
378    {
379      Debug.debugException(e);
380    }
381  }
382
383
384
385  /**
386   * Operates in a loop, waiting for a request to arrive from the client and
387   * handing it off to the request handler for processing.  This method is for
388   * internal use only and must not be invoked by external callers.
389   */
390  @InternalUseOnly()
391  @Override()
392  public void run()
393  {
394    try
395    {
396      while (true)
397      {
398        final LDAPMessage requestMessage;
399        try
400        {
401          requestMessage = LDAPMessage.readFrom(asn1Reader, false);
402          if (requestMessage == null)
403          {
404            // This indicates that the client has closed the connection without
405            // an unbind request.  It's not all that nice, but it isn't an error
406            // so we won't notify the exception handler.
407            try
408            {
409              close();
410            }
411            catch (final IOException ioe)
412            {
413              Debug.debugException(ioe);
414            }
415
416            return;
417          }
418        }
419        catch (final LDAPException le)
420        {
421          Debug.debugException(le);
422          close(le);
423          return;
424        }
425
426        try
427        {
428          final int messageID = requestMessage.getMessageID();
429          final List<Control> controls = requestMessage.getControls();
430
431          LDAPMessage responseMessage;
432          switch (requestMessage.getProtocolOpType())
433          {
434            case LDAPMessage.PROTOCOL_OP_TYPE_ABANDON_REQUEST:
435              requestHandler.processAbandonRequest(messageID,
436                   requestMessage.getAbandonRequestProtocolOp(), controls);
437              responseMessage = null;
438              break;
439
440            case LDAPMessage.PROTOCOL_OP_TYPE_ADD_REQUEST:
441              try
442              {
443                responseMessage = requestHandler.processAddRequest(messageID,
444                     requestMessage.getAddRequestProtocolOp(), controls);
445              }
446              catch (final Exception e)
447              {
448                Debug.debugException(e);
449                responseMessage = new LDAPMessage(messageID,
450                     new AddResponseProtocolOp(
451                          ResultCode.OTHER_INT_VALUE, null,
452                          ERR_CONN_REQUEST_HANDLER_FAILURE.get(
453                               StaticUtils.getExceptionMessage(e)),
454                          null));
455              }
456              break;
457
458            case LDAPMessage.PROTOCOL_OP_TYPE_BIND_REQUEST:
459              try
460              {
461                responseMessage = requestHandler.processBindRequest(messageID,
462                     requestMessage.getBindRequestProtocolOp(), controls);
463              }
464              catch (final Exception e)
465              {
466                Debug.debugException(e);
467                responseMessage = new LDAPMessage(messageID,
468                     new BindResponseProtocolOp(
469                          ResultCode.OTHER_INT_VALUE, null,
470                          ERR_CONN_REQUEST_HANDLER_FAILURE.get(
471                               StaticUtils.getExceptionMessage(e)),
472                          null, null));
473              }
474              break;
475
476            case LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST:
477              try
478              {
479                responseMessage = requestHandler.processCompareRequest(
480                     messageID, requestMessage.getCompareRequestProtocolOp(),
481                     controls);
482              }
483              catch (final Exception e)
484              {
485                Debug.debugException(e);
486                responseMessage = new LDAPMessage(messageID,
487                     new CompareResponseProtocolOp(
488                          ResultCode.OTHER_INT_VALUE, null,
489                          ERR_CONN_REQUEST_HANDLER_FAILURE.get(
490                               StaticUtils.getExceptionMessage(e)),
491                          null));
492              }
493              break;
494
495            case LDAPMessage.PROTOCOL_OP_TYPE_DELETE_REQUEST:
496              try
497              {
498                responseMessage = requestHandler.processDeleteRequest(messageID,
499                     requestMessage.getDeleteRequestProtocolOp(), controls);
500              }
501              catch (final Exception e)
502              {
503                Debug.debugException(e);
504                responseMessage = new LDAPMessage(messageID,
505                     new DeleteResponseProtocolOp(
506                          ResultCode.OTHER_INT_VALUE, null,
507                          ERR_CONN_REQUEST_HANDLER_FAILURE.get(
508                               StaticUtils.getExceptionMessage(e)),
509                          null));
510              }
511              break;
512
513            case LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST:
514              try
515              {
516                responseMessage = requestHandler.processExtendedRequest(
517                     messageID, requestMessage.getExtendedRequestProtocolOp(),
518                     controls);
519              }
520              catch (final Exception e)
521              {
522                Debug.debugException(e);
523                responseMessage = new LDAPMessage(messageID,
524                     new ExtendedResponseProtocolOp(
525                          ResultCode.OTHER_INT_VALUE, null,
526                          ERR_CONN_REQUEST_HANDLER_FAILURE.get(
527                               StaticUtils.getExceptionMessage(e)),
528                          null, null, null));
529              }
530              break;
531
532            case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_REQUEST:
533              try
534              {
535                responseMessage = requestHandler.processModifyRequest(messageID,
536                     requestMessage.getModifyRequestProtocolOp(), controls);
537              }
538              catch (final Exception e)
539              {
540                Debug.debugException(e);
541                responseMessage = new LDAPMessage(messageID,
542                     new ModifyResponseProtocolOp(
543                          ResultCode.OTHER_INT_VALUE, null,
544                          ERR_CONN_REQUEST_HANDLER_FAILURE.get(
545                               StaticUtils.getExceptionMessage(e)),
546                          null));
547              }
548              break;
549
550            case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST:
551              try
552              {
553                responseMessage = requestHandler.processModifyDNRequest(
554                     messageID, requestMessage.getModifyDNRequestProtocolOp(),
555                     controls);
556              }
557              catch (final Exception e)
558              {
559                Debug.debugException(e);
560                responseMessage = new LDAPMessage(messageID,
561                     new ModifyDNResponseProtocolOp(
562                          ResultCode.OTHER_INT_VALUE, null,
563                          ERR_CONN_REQUEST_HANDLER_FAILURE.get(
564                               StaticUtils.getExceptionMessage(e)),
565                          null));
566              }
567              break;
568
569            case LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST:
570              try
571              {
572                responseMessage = requestHandler.processSearchRequest(messageID,
573                     requestMessage.getSearchRequestProtocolOp(), controls);
574              }
575              catch (final Exception e)
576              {
577                Debug.debugException(e);
578                responseMessage = new LDAPMessage(messageID,
579                     new SearchResultDoneProtocolOp(
580                          ResultCode.OTHER_INT_VALUE, null,
581                          ERR_CONN_REQUEST_HANDLER_FAILURE.get(
582                               StaticUtils.getExceptionMessage(e)),
583                          null));
584              }
585              break;
586
587            case LDAPMessage.PROTOCOL_OP_TYPE_UNBIND_REQUEST:
588              requestHandler.processUnbindRequest(messageID,
589                   requestMessage.getUnbindRequestProtocolOp(), controls);
590              close();
591              return;
592
593            default:
594              close(new LDAPException(ResultCode.PROTOCOL_ERROR,
595                   ERR_CONN_INVALID_PROTOCOL_OP_TYPE.get(StaticUtils.toHex(
596                        requestMessage.getProtocolOpType()))));
597              return;
598          }
599
600          if (responseMessage != null)
601          {
602            try
603            {
604              sendMessage(responseMessage);
605            }
606            catch (final LDAPException le)
607            {
608              Debug.debugException(le);
609              close(le);
610              return;
611            }
612          }
613        }
614        catch (final Exception e)
615        {
616          close(new LDAPException(ResultCode.LOCAL_ERROR,
617               ERR_CONN_EXCEPTION_IN_REQUEST_HANDLER.get(
618                    String.valueOf(requestMessage),
619                    StaticUtils.getExceptionMessage(e))));
620          return;
621        }
622      }
623    }
624    finally
625    {
626      if (listener != null)
627      {
628        listener.connectionClosed(this);
629      }
630    }
631  }
632
633
634
635  /**
636   * Sends the provided message to the client.
637   *
638   * @param  message  The message to be written to the client.
639   *
640   * @throws  LDAPException  If a problem occurs while attempting to send the
641   *                         response to the client.
642   */
643  private synchronized void sendMessage(final LDAPMessage message)
644          throws LDAPException
645  {
646    // If we should suppress this response (which will only be because the
647    // response has already been sent through some other means, for example as
648    // part of StartTLS processing), then do so.
649    if (suppressNextResponse.compareAndSet(true, false))
650    {
651      return;
652    }
653
654    asn1Buffer.clear();
655
656    try
657    {
658      message.writeTo(asn1Buffer);
659    }
660    catch (final LDAPRuntimeException lre)
661    {
662      Debug.debugException(lre);
663      lre.throwLDAPException();
664    }
665
666    try
667    {
668      asn1Buffer.writeTo(outputStream);
669    }
670    catch (final IOException ioe)
671    {
672      Debug.debugException(ioe);
673
674      throw new LDAPException(ResultCode.LOCAL_ERROR,
675           ERR_CONN_SEND_MESSAGE_EXCEPTION.get(
676                StaticUtils.getExceptionMessage(ioe)),
677           ioe);
678    }
679    finally
680    {
681      if (asn1Buffer.zeroBufferOnClear())
682      {
683        asn1Buffer.clear();
684      }
685    }
686  }
687
688
689
690  /**
691   * Sends a search result entry message to the client with the provided
692   * information.
693   *
694   * @param  messageID   The message ID for the LDAP message to send to the
695   *                     client.  It must match the message ID of the associated
696   *                     search request.
697   * @param  protocolOp  The search result entry protocol op to include in the
698   *                     LDAP message to send to the client.  It must not be
699   *                     {@code null}.
700   * @param  controls    The set of controls to include in the response message.
701   *                     It may be empty or {@code null} if no controls should
702   *                     be included.
703   *
704   * @throws  LDAPException  If a problem occurs while attempting to send the
705   *                         provided response message.  If an exception is
706   *                         thrown, then the client connection will have been
707   *                         terminated.
708   */
709  public void sendSearchResultEntry(final int messageID,
710                   final SearchResultEntryProtocolOp protocolOp,
711                   final Control... controls)
712         throws LDAPException
713  {
714    if (searchEntryTransformers.isEmpty())
715    {
716      sendMessage(new LDAPMessage(messageID, protocolOp, controls));
717    }
718    else
719    {
720      Control[] c;
721      SearchResultEntryProtocolOp op = protocolOp;
722      if (controls == null)
723      {
724        c = EMPTY_CONTROL_ARRAY;
725      }
726      else
727      {
728        c = controls;
729      }
730
731      for (final SearchEntryTransformer t : searchEntryTransformers)
732      {
733        try
734        {
735          final ObjectPair<SearchResultEntryProtocolOp,Control[]> p =
736               t.transformEntry(messageID, op, c);
737          if (p == null)
738          {
739            return;
740          }
741
742          op = p.getFirst();
743          c  = p.getSecond();
744        }
745        catch (final Exception e)
746        {
747          Debug.debugException(e);
748          sendMessage(new LDAPMessage(messageID, protocolOp, c));
749          throw new LDAPException(ResultCode.LOCAL_ERROR,
750               ERR_CONN_SEARCH_ENTRY_TRANSFORMER_EXCEPTION.get(
751                    t.getClass().getName(), String.valueOf(op),
752                    StaticUtils.getExceptionMessage(e)),
753               e);
754        }
755      }
756
757      sendMessage(new LDAPMessage(messageID, op, c));
758    }
759  }
760
761
762
763  /**
764   * Sends a search result entry message to the client with the provided
765   * information.
766   *
767   * @param  messageID  The message ID for the LDAP message to send to the
768   *                    client.  It must match the message ID of the associated
769   *                    search request.
770   * @param  entry      The entry to return to the client.  It must not be
771   *                    {@code null}.
772   * @param  controls   The set of controls to include in the response message.
773   *                    It may be empty or {@code null} if no controls should be
774   *                    included.
775   *
776   * @throws  LDAPException  If a problem occurs while attempting to send the
777   *                         provided response message.  If an exception is
778   *                         thrown, then the client connection will have been
779   *                         terminated.
780   */
781  public void sendSearchResultEntry(final int messageID, final Entry entry,
782                                    final Control... controls)
783         throws LDAPException
784  {
785    sendSearchResultEntry(messageID,
786         new SearchResultEntryProtocolOp(entry.getDN(),
787              new ArrayList<Attribute>(entry.getAttributes())),
788         controls);
789  }
790
791
792
793  /**
794   * Sends a search result reference message to the client with the provided
795   * information.
796   *
797   * @param  messageID   The message ID for the LDAP message to send to the
798   *                     client.  It must match the message ID of the associated
799   *                     search request.
800   * @param  protocolOp  The search result reference protocol op to include in
801   *                     the LDAP message to send to the client.
802   * @param  controls    The set of controls to include in the response message.
803   *                     It may be empty or {@code null} if no controls should
804   *                     be included.
805   *
806   * @throws  LDAPException  If a problem occurs while attempting to send the
807   *                         provided response message.  If an exception is
808   *                         thrown, then the client connection will have been
809   *                         terminated.
810   */
811  public void sendSearchResultReference(final int messageID,
812                   final SearchResultReferenceProtocolOp protocolOp,
813                   final Control... controls)
814         throws LDAPException
815  {
816    if (searchReferenceTransformers.isEmpty())
817    {
818      sendMessage(new LDAPMessage(messageID, protocolOp, controls));
819    }
820    else
821    {
822      Control[] c;
823      SearchResultReferenceProtocolOp op = protocolOp;
824      if (controls == null)
825      {
826        c = EMPTY_CONTROL_ARRAY;
827      }
828      else
829      {
830        c = controls;
831      }
832
833      for (final SearchReferenceTransformer t : searchReferenceTransformers)
834      {
835        try
836        {
837          final ObjectPair<SearchResultReferenceProtocolOp,Control[]> p =
838               t.transformReference(messageID, op, c);
839          if (p == null)
840          {
841            return;
842          }
843
844          op = p.getFirst();
845          c  = p.getSecond();
846        }
847        catch (final Exception e)
848        {
849          Debug.debugException(e);
850          sendMessage(new LDAPMessage(messageID, protocolOp, c));
851          throw new LDAPException(ResultCode.LOCAL_ERROR,
852               ERR_CONN_SEARCH_REFERENCE_TRANSFORMER_EXCEPTION.get(
853                    t.getClass().getName(), String.valueOf(op),
854                    StaticUtils.getExceptionMessage(e)),
855               e);
856        }
857      }
858
859      sendMessage(new LDAPMessage(messageID, op, c));
860    }
861  }
862
863
864
865  /**
866   * Sends an intermediate response message to the client with the provided
867   * information.
868   *
869   * @param  messageID   The message ID for the LDAP message to send to the
870   *                     client.  It must match the message ID of the associated
871   *                     search request.
872   * @param  protocolOp  The intermediate response protocol op to include in the
873   *                     LDAP message to send to the client.
874   * @param  controls    The set of controls to include in the response message.
875   *                     It may be empty or {@code null} if no controls should
876   *                     be included.
877   *
878   * @throws  LDAPException  If a problem occurs while attempting to send the
879   *                         provided response message.  If an exception is
880   *                         thrown, then the client connection will have been
881   *                         terminated.
882   */
883  public void sendIntermediateResponse(final int messageID,
884                   final IntermediateResponseProtocolOp protocolOp,
885                   final Control... controls)
886         throws LDAPException
887  {
888    if (intermediateResponseTransformers.isEmpty())
889    {
890      sendMessage(new LDAPMessage(messageID, protocolOp, controls));
891    }
892    else
893    {
894      Control[] c;
895      IntermediateResponseProtocolOp op = protocolOp;
896      if (controls == null)
897      {
898        c = EMPTY_CONTROL_ARRAY;
899      }
900      else
901      {
902        c = controls;
903      }
904
905      for (final IntermediateResponseTransformer t :
906           intermediateResponseTransformers)
907      {
908        try
909        {
910          final ObjectPair<IntermediateResponseProtocolOp,Control[]> p =
911               t.transformIntermediateResponse(messageID, op, c);
912          if (p == null)
913          {
914            return;
915          }
916
917          op = p.getFirst();
918          c  = p.getSecond();
919        }
920        catch (final Exception e)
921        {
922          Debug.debugException(e);
923          sendMessage(new LDAPMessage(messageID, protocolOp, c));
924          throw new LDAPException(ResultCode.LOCAL_ERROR,
925               ERR_CONN_INTERMEDIATE_RESPONSE_TRANSFORMER_EXCEPTION.get(
926                    t.getClass().getName(), String.valueOf(op),
927                    StaticUtils.getExceptionMessage(e)),
928               e);
929        }
930      }
931
932      sendMessage(new LDAPMessage(messageID, op, c));
933    }
934  }
935
936
937
938  /**
939   * Sends an unsolicited notification message to the client with the provided
940   * extended result.
941   *
942   * @param  result  The extended result to use for the unsolicited
943   *                 notification.
944   *
945   * @throws  LDAPException  If a problem occurs while attempting to send the
946   *                         unsolicited notification.  If an exception is
947   *                         thrown, then the client connection will have been
948   *                         terminated.
949   */
950  public void sendUnsolicitedNotification(final ExtendedResult result)
951         throws LDAPException
952  {
953    sendUnsolicitedNotification(
954         new ExtendedResponseProtocolOp(result.getResultCode().intValue(),
955              result.getMatchedDN(), result.getDiagnosticMessage(),
956              StaticUtils.toList(result.getReferralURLs()), result.getOID(),
957              result.getValue()),
958         result.getResponseControls()
959    );
960  }
961
962
963
964  /**
965   * Sends an unsolicited notification message to the client with the provided
966   * information.
967   *
968   * @param  extendedResponse  The extended response to use for the unsolicited
969   *                           notification.
970   * @param  controls          The set of controls to include with the
971   *                           unsolicited notification.  It may be empty or
972   *                           {@code null} if no controls should be included.
973   *
974   * @throws  LDAPException  If a problem occurs while attempting to send the
975   *                         unsolicited notification.  If an exception is
976   *                         thrown, then the client connection will have been
977   *                         terminated.
978   */
979  public void sendUnsolicitedNotification(
980                   final ExtendedResponseProtocolOp extendedResponse,
981                   final Control... controls)
982         throws LDAPException
983  {
984    sendMessage(new LDAPMessage(0, extendedResponse, controls));
985  }
986
987
988
989  /**
990   * Retrieves the socket used to communicate with the client.
991   *
992   * @return  The socket used to communicate with the client.
993   */
994  public synchronized Socket getSocket()
995  {
996    return socket;
997  }
998
999
1000
1001  /**
1002   * Attempts to convert this unencrypted connection to one that uses TLS
1003   * encryption, as would be used during the course of invoking the StartTLS
1004   * extended operation.  If this is called, then the response that would have
1005   * been returned from the associated request will be suppressed, so the
1006   * returned output stream must be used to send the appropriate response to
1007   * the client.
1008   *
1009   * @param  f  The SSL socket factory that will be used to convert the existing
1010   *            {@code Socket} to an {@code SSLSocket}.
1011   *
1012   * @return  An output stream that can be used to send a clear-text message to
1013   *          the client (e.g., the StartTLS response message).
1014   *
1015   * @throws  LDAPException  If a problem is encountered while trying to convert
1016   *                         the existing socket to an SSL socket.  If this is
1017   *                         thrown, then the connection will have been closed.
1018   */
1019  public synchronized OutputStream convertToTLS(final SSLSocketFactory f)
1020         throws LDAPException
1021  {
1022    final OutputStream clearOutputStream = outputStream;
1023
1024    final Socket origSocket = socket;
1025    final String hostname   = origSocket.getInetAddress().getHostName();
1026    final int port          = origSocket.getPort();
1027
1028    try
1029    {
1030      synchronized (f)
1031      {
1032        socket = f.createSocket(socket, hostname, port, true);
1033      }
1034      ((SSLSocket) socket).setUseClientMode(false);
1035      outputStream = socket.getOutputStream();
1036      asn1Reader = new ASN1StreamReader(socket.getInputStream());
1037      suppressNextResponse.set(true);
1038      return clearOutputStream;
1039    }
1040    catch (final Exception e)
1041    {
1042      Debug.debugException(e);
1043
1044      final LDAPException le = new LDAPException(ResultCode.LOCAL_ERROR,
1045           ERR_CONN_CONVERT_TO_TLS_FAILURE.get(
1046                StaticUtils.getExceptionMessage(e)),
1047           e);
1048
1049      close(le);
1050
1051      throw le;
1052    }
1053  }
1054
1055
1056
1057  /**
1058   * Retrieves the connection ID that has been assigned to this connection by
1059   * the associated listener.
1060   *
1061   * @return  The connection ID that has been assigned to this connection by
1062   *          the associated listener, or -1 if it is not associated with a
1063   *          listener.
1064   */
1065  public long getConnectionID()
1066  {
1067    return connectionID;
1068  }
1069
1070
1071
1072  /**
1073   * Adds the provided search entry transformer to this client connection.
1074   *
1075   * @param  t  A search entry transformer to be used to intercept and/or alter
1076   *            search result entries before they are returned to the client.
1077   */
1078  public void addSearchEntryTransformer(final SearchEntryTransformer t)
1079  {
1080    searchEntryTransformers.add(t);
1081  }
1082
1083
1084
1085  /**
1086   * Removes the provided search entry transformer from this client connection.
1087   *
1088   * @param  t  The search entry transformer to be removed.
1089   */
1090  public void removeSearchEntryTransformer(final SearchEntryTransformer t)
1091  {
1092    searchEntryTransformers.remove(t);
1093  }
1094
1095
1096
1097  /**
1098   * Adds the provided search reference transformer to this client connection.
1099   *
1100   * @param  t  A search reference transformer to be used to intercept and/or
1101   *            alter search result references before they are returned to the
1102   *            client.
1103   */
1104  public void addSearchReferenceTransformer(final SearchReferenceTransformer t)
1105  {
1106    searchReferenceTransformers.add(t);
1107  }
1108
1109
1110
1111  /**
1112   * Removes the provided search reference transformer from this client
1113   * connection.
1114   *
1115   * @param  t  The search reference transformer to be removed.
1116   */
1117  public void removeSearchReferenceTransformer(
1118                   final SearchReferenceTransformer t)
1119  {
1120    searchReferenceTransformers.remove(t);
1121  }
1122
1123
1124
1125  /**
1126   * Adds the provided intermediate response transformer to this client
1127   * connection.
1128   *
1129   * @param  t  An intermediate response transformer to be used to intercept
1130   *            and/or alter intermediate responses before they are returned to
1131   *            the client.
1132   */
1133  public void addIntermediateResponseTransformer(
1134                   final IntermediateResponseTransformer t)
1135  {
1136    intermediateResponseTransformers.add(t);
1137  }
1138
1139
1140
1141  /**
1142   * Removes the provided intermediate response transformer from this client
1143   * connection.
1144   *
1145   * @param  t  The intermediate response transformer to be removed.
1146   */
1147  public void removeIntermediateResponseTransformer(
1148                   final IntermediateResponseTransformer t)
1149  {
1150    intermediateResponseTransformers.remove(t);
1151  }
1152}