001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2014-2015 ForgeRock AS
026 */
027package org.opends.server.protocols.internal;
028
029import java.io.IOException;
030import java.io.InputStream;
031import java.io.OutputStream;
032import java.util.List;
033
034import org.forgerock.i18n.LocalizableMessage;
035import org.forgerock.opendj.io.ASN1;
036import org.forgerock.opendj.io.ASN1Reader;
037import org.forgerock.opendj.ldap.ByteSequenceReader;
038import org.forgerock.opendj.ldap.ByteString;
039import org.forgerock.opendj.ldap.ByteStringBuilder;
040import org.opends.server.core.*;
041import org.opends.server.protocols.ldap.*;
042import org.opends.server.types.*;
043
044import static org.forgerock.opendj.ldap.DecodeException.*;
045import static org.opends.messages.ProtocolMessages.*;
046import static org.opends.server.protocols.internal.InternalClientConnection.*;
047import static org.opends.server.protocols.internal.Requests.*;
048import static org.opends.server.protocols.ldap.LDAPConstants.*;
049import static org.opends.server.util.ServerConstants.*;
050
051/**
052 * This class provides an implementation of a
053 * {@code java.io.OutputStream} that can be used to facilitate
054 * internal communication with the Directory Server.  On the backend,
055 * data written to this output stream will be first decoded as an
056 * ASN.1 element and then as an LDAP message.  That LDAP message will
057 * be converted to an internal operation which will then be processed
058 * and the result returned to the client via the input stream on the
059 * other side of the associated internal LDAP socket.
060 */
061@org.opends.server.types.PublicAPI(
062     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
063     mayInstantiate=false,
064     mayExtend=false,
065     mayInvoke=true)
066public final class InternalLDAPOutputStream
067       extends OutputStream
068       implements InternalSearchListener
069{
070  /** Indicates whether this stream has been closed. */
071  private boolean closed;
072
073  private final ASN1Reader reader;
074
075  /** The internal LDAP socket with which this output stream is associated. */
076  private final InternalLDAPSocket socket;
077
078  /** The immediate data being written. */
079  private ByteSequenceReader byteBuffer;
080
081  /**
082   * The save buffer used to store any unprocessed data waiting
083   * to be read as ASN.1 elements. (Usually due to writing incomplete
084   * ASN.1 elements.)
085   */
086  private final ByteStringBuilder saveBuffer;
087
088  private final ByteSequenceReader saveBufferReader;
089
090  /**
091   * An adaptor class for reading from a save buffer and the bytes
092   * being written sequentially using the InputStream interface.
093   *
094   * Since the bytes being written are only available duing the write
095   * call, any unused data will be appended to the save buffer before
096   * returning from the write method. This reader will always read the
097   * save buffer first before the actual bytes being written to ensure
098   * bytes are read in the same order as they are written.
099   */
100  private class CombinedBufferInputStream extends InputStream
101  {
102    /** {@inheritDoc} */
103    @Override
104    public int available()
105    {
106      // The number of available bytes is the sum of the save buffer
107      // and the last read data in the NIO ByteStringBuilder.
108      return saveBufferReader.remaining() + byteBuffer.remaining();
109    }
110
111    /** {@inheritDoc} */
112    @Override
113    public int read()
114    {
115      if(saveBufferReader.remaining() > 0)
116      {
117        // Try saved buffer first
118        return 0xFF & saveBufferReader.readByte();
119      }
120      if(byteBuffer.remaining() > 0)
121      {
122        // Must still be on the channel buffer
123        return 0xFF & byteBuffer.readByte();
124      }
125
126      return -1;
127    }
128
129    /** {@inheritDoc} */
130    @Override
131    public int read(byte[] bytes)
132    {
133      return read(bytes, 0, bytes.length);
134    }
135
136    /** {@inheritDoc} */
137    @Override
138    public int read(byte[] value, int off, int length)
139    {
140      int bytesCopied=0;
141      int len;
142      if(saveBufferReader.remaining() > 0)
143      {
144        // Copy out of the last saved buffer first
145        len = Math.min(saveBufferReader.remaining(), length);
146        saveBufferReader.readBytes(value, off, len);
147        bytesCopied += len;
148      }
149      if(bytesCopied < length && byteBuffer.remaining() > 0)
150      {
151        // Copy out of the channel buffer if we haven't got
152        // everything we needed.
153        len = Math.min(byteBuffer.remaining(), length - bytesCopied);
154        byteBuffer.readBytes(value, off + bytesCopied, len);
155        bytesCopied += len;
156      }
157      return bytesCopied;
158    }
159
160    /** {@inheritDoc} */
161    @Override
162    public long skip(long length)
163    {
164      int bytesSkipped=0;
165      int len;
166      if(saveBufferReader.remaining() > 0)
167      {
168        // Skip in the last saved buffer first
169        len = Math.min(saveBufferReader.remaining(), (int)length);
170        saveBufferReader.position(saveBufferReader.position() + len);
171        bytesSkipped += len;
172      }
173      if(bytesSkipped < length && byteBuffer.remaining() > 0)
174      {
175        //Skip in the channel buffer if we haven't skipped enough.
176        len = Math.min(byteBuffer.remaining(),
177            (int)length - bytesSkipped);
178        byteBuffer.position(byteBuffer.position() + len);
179        bytesSkipped += len;
180      }
181      return bytesSkipped;
182    }
183  }
184
185  /**
186   * Creates a new instance of an internal LDAP output stream that is
187   * associated with the provided internal LDAP socket.
188   *
189   * @param  socket  The internal LDAP socket that will be serviced by
190   *                 this internal LDAP output stream.
191   */
192  public InternalLDAPOutputStream(InternalLDAPSocket socket)
193  {
194    this.socket = socket;
195    this.closed = false;
196    this.saveBuffer = new ByteStringBuilder();
197    this.saveBufferReader = saveBuffer.asReader();
198
199    CombinedBufferInputStream bufferStream =
200        new CombinedBufferInputStream();
201    this.reader = ASN1.getReader(bufferStream);
202  }
203
204
205
206  /**
207   * Closes this output stream, its associated socket, and the
208   * socket's associated input stream.
209   */
210  @Override
211  public void close()
212  {
213    socket.close();
214  }
215
216
217
218  /**
219   * Closes this output stream through an internal mechanism that will
220   * not cause an infinite recursion loop by trying to also close the
221   * output stream.
222   */
223  @org.opends.server.types.PublicAPI(
224       stability=org.opends.server.types.StabilityLevel.PRIVATE,
225       mayInstantiate=false,
226       mayExtend=false,
227       mayInvoke=false)
228  void closeInternal()
229  {
230    closed = true;
231  }
232
233
234
235  /**
236   * Flushes this output stream and forces any buffered data to be
237   * written out.  This will have no effect, since this output
238   * stream implementation does not use buffering.
239   */
240  @Override
241  public void flush()
242  {
243    // No implementation is required.
244  }
245
246
247
248  /**
249   * Writes the contents of the provided byte array to this output
250   * stream.
251   *
252   * @param  b  The byte array to be written.
253   *
254   * @throws  IOException  If the output stream is closed, or if there
255   *                       is a problem with the data being written.
256   */
257  @Override
258  public void write(byte[] b)
259         throws IOException
260  {
261    write(b, 0, b.length);
262  }
263
264
265
266  /**
267   * Writes the specified portion of the data in the provided byte
268   * array to this output stream.  Any data written will be
269   * accumulated until a complete ASN.1 element is available, at which
270   * point it will be decoded as an LDAP message and converted to an
271   * internal operation that will be processed.
272   *
273   * @param  b    The byte array containing the data to be read.
274   * @param  off  The position in the array at which to start reading
275   *              data.
276   * @param  len  The number of bytes to read from the array.
277   *
278   * @throws  IOException  If the output stream is closed, or if there
279   *                       is a problem with the data being written.
280   */
281  @Override
282  public synchronized void write(byte[] b, int off, int len)
283         throws IOException
284  {
285    if (closed)
286    {
287      LocalizableMessage m = ERR_INTERNALOS_CLOSED.get();
288      throw new IOException(m.toString());
289    }
290
291    byteBuffer = ByteString.wrap(b, off, len).asReader();
292
293    try
294    {
295      while(reader.elementAvailable())
296      {
297        LDAPMessage msg = LDAPReader.readMessage(reader);
298        processMessage(msg);
299      }
300    }
301    catch(Exception e)
302    {
303      throw new IOException(e.getMessage());
304    }
305
306    // Clear the save buffer if we have read all of it
307    if(saveBufferReader.remaining() == 0)
308    {
309      saveBuffer.clear();
310      saveBufferReader.rewind();
311    }
312
313    // Append any unused data in the channel buffer to the save buffer
314    if(byteBuffer.remaining() > 0)
315    {
316      saveBuffer.appendBytes(byteBuffer, byteBuffer.remaining());
317    }
318  }
319
320
321
322  /**
323   * Writes a single byte of data to this output stream.  If the byte
324   * written completes an ASN.1 element that was in progress, then it
325   * will be decoded as an LDAP message and converted to an internal
326   * operation that will be processed.  Otherwise, the data will be
327   * accumulated until a complete element can be formed.
328   *
329   * @param  b The byte to be written.
330   *
331   * @throws  IOException  If the output stream is closed, or if there
332   *                       is a problem with the data being written.
333   */
334  @Override
335  public synchronized void write(int b)
336         throws IOException
337  {
338    write(new byte[]{(byte)b}, 0, 1);
339  }
340
341
342
343  /**
344   * Processes the provided ASN.1 element by decoding it as an LDAP
345   * message, converting that to an internal operation, and sending
346   * the appropriate response message(s) to the client through the
347   * corresponding internal LDAP input stream.
348   *
349   * @param  message The LDAP message to process.
350   *
351   * @throws  IOException  If a problem occurs while attempting to
352   *                       decode the provided ASN.1 element as an
353   *                       LDAP message.
354   */
355  private void processMessage(LDAPMessage message)
356          throws IOException
357  {
358    switch (message.getProtocolOpType())
359    {
360      case OP_TYPE_ABANDON_REQUEST:
361        // No action is required.
362        return;
363
364      case OP_TYPE_ADD_REQUEST:
365        processAddOperation(message);
366        break;
367
368      case OP_TYPE_BIND_REQUEST:
369        processBindOperation(message);
370        break;
371
372      case OP_TYPE_COMPARE_REQUEST:
373        processCompareOperation(message);
374        break;
375
376
377      case OP_TYPE_DELETE_REQUEST:
378        processDeleteOperation(message);
379        break;
380
381
382      case OP_TYPE_EXTENDED_REQUEST:
383        processExtendedOperation(message);
384        break;
385
386
387      case OP_TYPE_MODIFY_REQUEST:
388        processModifyOperation(message);
389        break;
390
391
392      case OP_TYPE_MODIFY_DN_REQUEST:
393        processModifyDNOperation(message);
394        break;
395
396
397      case OP_TYPE_SEARCH_REQUEST:
398        processSearchOperation(message);
399        break;
400
401
402      case OP_TYPE_UNBIND_REQUEST:
403        socket.close();
404        break;
405
406
407      default:
408        LocalizableMessage m = ERR_INTERNALOS_INVALID_REQUEST.get(
409                         message.getProtocolElementName());
410        throw new IOException(m.toString());
411    }
412  }
413
414
415
416  /**
417   * Processes the content of the provided LDAP message as an add
418   * operation and returns the appropriate result to the client.
419   *
420   * @param  message  The LDAP message containing the request to
421   *                  process.
422   *
423   * @throws  IOException  If a problem occurs while attempting to
424   *                       process the operation.
425   */
426  private void processAddOperation(LDAPMessage message)
427          throws IOException
428  {
429    int messageID = message.getMessageID();
430    AddRequestProtocolOp request = message.getAddRequestProtocolOp();
431
432    InternalClientConnection conn = socket.getConnection();
433    AddOperationBasis op =
434         new AddOperationBasis(conn, InternalClientConnection.nextOperationID(),
435                               messageID, message.getControls(),
436                               request.getDN(),
437                               request.getAttributes());
438    op.run();
439
440    AddResponseProtocolOp addResponse =
441         new AddResponseProtocolOp(op.getResultCode().intValue(),
442                                   op.getErrorMessage().toMessage(),
443                                   op.getMatchedDN(),
444                                   op.getReferralURLs());
445    List<Control> responseControls = op.getResponseControls();
446
447    socket.getInputStream().addLDAPMessage(
448         new LDAPMessage(messageID, addResponse, responseControls));
449  }
450
451
452
453  /**
454   * Processes the content of the provided LDAP message as a bind
455   * operation and returns the appropriate result to the client.
456   *
457   * @param  message  The LDAP message containing the request to
458   *                  process.
459   *
460   * @throws  IOException  If a problem occurs while attempting to
461   *                       process the operation.
462   */
463  private void processBindOperation(LDAPMessage message)
464          throws IOException
465  {
466    int messageID = message.getMessageID();
467    BindRequestProtocolOp request =
468         message.getBindRequestProtocolOp();
469
470    if (request.getAuthenticationType() == AuthenticationType.SASL)
471    {
472      LocalizableMessage m = ERR_INTERNALOS_SASL_BIND_NOT_SUPPORTED.get();
473      BindResponseProtocolOp bindResponse =
474           new BindResponseProtocolOp(
475                    LDAPResultCode.UNWILLING_TO_PERFORM, m);
476      socket.getInputStream().addLDAPMessage(
477           new LDAPMessage(messageID, bindResponse));
478      return;
479    }
480
481    InternalClientConnection conn = socket.getConnection();
482    BindOperationBasis op =
483         new BindOperationBasis(conn, nextOperationID(),
484                  messageID, message.getControls(),
485                  String.valueOf(request.getProtocolVersion()),
486                  request.getDN(), request.getSimplePassword());
487    op.run();
488
489    BindResponseProtocolOp bindResponse =
490         new BindResponseProtocolOp(op.getResultCode().intValue(),
491                                    op.getErrorMessage().toMessage(),
492                                    op.getMatchedDN(),
493                                    op.getReferralURLs());
494    List<Control> responseControls = op.getResponseControls();
495
496    if (bindResponse.getResultCode() == LDAPResultCode.SUCCESS)
497    {
498      socket.setConnection(new InternalClientConnection(
499           op.getAuthenticationInfo()));
500    }
501
502    socket.getInputStream().addLDAPMessage(
503         new LDAPMessage(messageID, bindResponse, responseControls));
504  }
505
506
507
508  /**
509   * Processes the content of the provided LDAP message as a compare
510   * operation and returns the appropriate result to the client.
511   *
512   * @param  message  The LDAP message containing the request to
513   *                  process.
514   *
515   * @throws  IOException  If a problem occurs while attempting to
516   *                       process the operation.
517   */
518  private void processCompareOperation(LDAPMessage message)
519          throws IOException
520  {
521    int messageID = message.getMessageID();
522    CompareRequestProtocolOp request =
523         message.getCompareRequestProtocolOp();
524
525    InternalClientConnection conn = socket.getConnection();
526    CompareOperationBasis op =
527         new CompareOperationBasis(conn, nextOperationID(),
528                  messageID, message.getControls(), request.getDN(),
529                  request.getAttributeType(),
530                  request.getAssertionValue());
531    op.run();
532
533    CompareResponseProtocolOp compareResponse =
534         new CompareResponseProtocolOp(
535                  op.getResultCode().intValue(),
536                  op.getErrorMessage().toMessage(),
537                  op.getMatchedDN(),
538                  op.getReferralURLs());
539    List<Control> responseControls = op.getResponseControls();
540
541    socket.getInputStream().addLDAPMessage(
542         new LDAPMessage(messageID, compareResponse,
543                         responseControls));
544  }
545
546
547
548  /**
549   * Processes the content of the provided LDAP message as a delete
550   * operation and returns the appropriate result to the client.
551   *
552   * @param  message  The LDAP message containing the request to
553   *                  process.
554   *
555   * @throws  IOException  If a problem occurs while attempting to
556   *                       process the operation.
557   */
558  private void processDeleteOperation(LDAPMessage message)
559          throws IOException
560  {
561    int messageID = message.getMessageID();
562    DeleteRequestProtocolOp request =
563         message.getDeleteRequestProtocolOp();
564
565    InternalClientConnection conn = socket.getConnection();
566    DeleteOperationBasis op =
567         new DeleteOperationBasis(conn, nextOperationID(),
568                  messageID, message.getControls(), request.getDN());
569    op.run();
570
571    DeleteResponseProtocolOp deleteResponse =
572         new DeleteResponseProtocolOp(
573                  op.getResultCode().intValue(),
574                  op.getErrorMessage().toMessage(),
575                  op.getMatchedDN(),
576                  op.getReferralURLs());
577    List<Control> responseControls = op.getResponseControls();
578
579    socket.getInputStream().addLDAPMessage(
580         new LDAPMessage(messageID, deleteResponse,
581                         responseControls));
582  }
583
584
585
586  /**
587   * Processes the content of the provided LDAP message as an extended
588   * operation and returns the appropriate result to the client.
589   *
590   * @param  message  The LDAP message containing the request to
591   *                  process.
592   *
593   * @throws  IOException  If a problem occurs while attempting to
594   *                       process the operation.
595   */
596  private void processExtendedOperation(LDAPMessage message)
597          throws IOException
598  {
599    int messageID = message.getMessageID();
600    ExtendedRequestProtocolOp request =
601         message.getExtendedRequestProtocolOp();
602    if (request.getOID().equals(OID_START_TLS_REQUEST))
603    {
604      LocalizableMessage m = ERR_INTERNALOS_STARTTLS_NOT_SUPPORTED.get();
605      ExtendedResponseProtocolOp extendedResponse =
606           new ExtendedResponseProtocolOp(
607                    LDAPResultCode.UNWILLING_TO_PERFORM, m);
608      socket.getInputStream().addLDAPMessage(
609           new LDAPMessage(messageID, extendedResponse));
610      return;
611    }
612
613    InternalClientConnection conn = socket.getConnection();
614    ExtendedOperationBasis op =
615         new ExtendedOperationBasis(conn, nextOperationID(),
616                  messageID, message.getControls(), request.getOID(),
617                  request.getValue());
618    op.run();
619
620    ExtendedResponseProtocolOp extendedResponse =
621         new ExtendedResponseProtocolOp(
622                  op.getResultCode().intValue(),
623                  op.getErrorMessage().toMessage(),
624                  op.getMatchedDN(),
625                  op.getReferralURLs(), op.getResponseOID(),
626                  op.getResponseValue());
627    List<Control> responseControls = op.getResponseControls();
628
629    socket.getInputStream().addLDAPMessage(
630         new LDAPMessage(messageID, extendedResponse,
631                         responseControls));
632  }
633
634
635
636  /**
637   * Processes the content of the provided LDAP message as a modify
638   * operation and returns the appropriate result to the client.
639   *
640   * @param  message  The LDAP message containing the request to
641   *                  process.
642   *
643   * @throws  IOException  If a problem occurs while attempting to
644   *                       process the operation.
645   */
646  private void processModifyOperation(LDAPMessage message)
647          throws IOException
648  {
649    int messageID = message.getMessageID();
650    ModifyRequestProtocolOp request =
651         message.getModifyRequestProtocolOp();
652
653    InternalClientConnection conn = socket.getConnection();
654    ModifyOperationBasis op =
655         new ModifyOperationBasis(conn, nextOperationID(),
656                  messageID, message.getControls(), request.getDN(),
657                  request.getModifications());
658    op.run();
659
660    ModifyResponseProtocolOp modifyResponse =
661         new ModifyResponseProtocolOp(
662                  op.getResultCode().intValue(),
663                  op.getErrorMessage().toMessage(),
664                  op.getMatchedDN(),
665                  op.getReferralURLs());
666    List<Control> responseControls = op.getResponseControls();
667
668    socket.getInputStream().addLDAPMessage(
669         new LDAPMessage(messageID, modifyResponse,
670                         responseControls));
671  }
672
673
674
675  /**
676   * Processes the content of the provided LDAP message as a modify DN
677   * operation and returns the appropriate result to the client.
678   *
679   * @param  message  The LDAP message containing the request to
680   *                  process.
681   *
682   * @throws  IOException  If a problem occurs while attempting to
683   *                       process the operation.
684   */
685  private void processModifyDNOperation(LDAPMessage message)
686          throws IOException
687  {
688    int messageID = message.getMessageID();
689    ModifyDNRequestProtocolOp request =
690         message.getModifyDNRequestProtocolOp();
691
692    InternalClientConnection conn = socket.getConnection();
693    ModifyDNOperationBasis op =
694         new ModifyDNOperationBasis(conn, nextOperationID(),
695               messageID, message.getControls(), request.getEntryDN(),
696               request.getNewRDN(), request.deleteOldRDN(),
697               request.getNewSuperior());
698    op.run();
699
700    ModifyDNResponseProtocolOp modifyDNResponse =
701         new ModifyDNResponseProtocolOp(
702                  op.getResultCode().intValue(),
703                  op.getErrorMessage().toMessage(),
704                  op.getMatchedDN(),
705                  op.getReferralURLs());
706    List<Control> responseControls = op.getResponseControls();
707
708    socket.getInputStream().addLDAPMessage(
709         new LDAPMessage(messageID, modifyDNResponse,
710                         responseControls));
711  }
712
713
714
715  /**
716   * Processes the content of the provided LDAP message as a search
717   * operation and returns the appropriate result to the client.
718   *
719   * @param  message  The LDAP message containing the request to
720   *                  process.
721   *
722   * @throws  IOException  If a problem occurs while attempting to
723   *                       process the operation.
724   */
725  private void processSearchOperation(LDAPMessage message)
726          throws IOException
727  {
728    int messageID = message.getMessageID();
729    SearchRequestProtocolOp request = message.getSearchRequestProtocolOp();
730
731    InternalClientConnection conn = socket.getConnection();
732    DN baseDN = null;
733    SearchFilter filter;
734    try
735    {
736      baseDN = DN.valueOf(request.getBaseDN().toString());
737      filter = request.getFilter().toSearchFilter();
738    }
739    catch (DirectoryException e)
740    {
741      final String cause = baseDN == null ? "baseDN" : "filter";
742      throw error(LocalizableMessage.raw("Could not decode " + cause), e);
743    }
744    SearchRequest sr = newSearchRequest(baseDN, request.getScope(), filter)
745        .setDereferenceAliasesPolicy(request.getDereferencePolicy())
746        .setSizeLimit(request.getSizeLimit())
747        .setTimeLimit(request.getTimeLimit())
748        .setTypesOnly(request.getTypesOnly())
749        .addAttribute(request.getAttributes())
750        .addControl(message.getControls());
751    InternalSearchOperation op = new InternalSearchOperation(
752        conn, nextOperationID(), messageID, sr, this);
753    op.run();
754
755    SearchResultDoneProtocolOp searchDone =
756         new SearchResultDoneProtocolOp(
757                  op.getResultCode().intValue(),
758                  op.getErrorMessage().toMessage(),
759                  op.getMatchedDN(),
760                  op.getReferralURLs());
761    List<Control> responseControls = op.getResponseControls();
762
763    socket.getInputStream().addLDAPMessage(
764         new LDAPMessage(messageID, searchDone, responseControls));
765  }
766
767
768
769  /**
770   * Performs any processing necessary for the provided search result
771   * entry.
772   *
773   * @param  searchOperation  The internal search operation being
774   *                          processed.
775   * @param  searchEntry      The matching search result entry to be
776   *                          processed.
777   */
778  @Override
779  @org.opends.server.types.PublicAPI(
780       stability=org.opends.server.types.StabilityLevel.PRIVATE,
781       mayInstantiate=false,
782       mayExtend=false,
783       mayInvoke=false)
784  public void handleInternalSearchEntry(
785                   InternalSearchOperation searchOperation,
786                   SearchResultEntry searchEntry)
787  {
788    List<Control> entryControls = searchEntry.getControls();
789
790    SearchResultEntryProtocolOp entry =
791         new SearchResultEntryProtocolOp(searchEntry);
792
793    socket.getInputStream().addLDAPMessage(
794         new LDAPMessage(searchOperation.getMessageID(), entry, entryControls));
795  }
796
797
798
799  /**
800   * Performs any processing necessary for the provided search result reference.
801   *
802   * @param  searchOperation  The internal search operation being processed.
803   * @param  searchReference  The search result reference to be processed.
804   */
805  @Override
806  @org.opends.server.types.PublicAPI(
807       stability=org.opends.server.types.StabilityLevel.PRIVATE,
808       mayInstantiate=false,
809       mayExtend=false,
810       mayInvoke=false)
811  public void handleInternalSearchReference(
812                   InternalSearchOperation searchOperation,
813                   SearchResultReference searchReference)
814  {
815    List<Control> entryControls = searchReference.getControls();
816
817    SearchResultReferenceProtocolOp reference =
818         new SearchResultReferenceProtocolOp(searchReference);
819
820    socket.getInputStream().addLDAPMessage(
821         new LDAPMessage(searchOperation.getMessageID(), reference,
822                         entryControls));
823  }
824
825
826
827  /**
828   * Retrieves a string representation of this internal LDAP socket.
829   *
830   * @return  A string representation of this internal LDAP socket.
831   */
832  @Override
833  public String toString()
834  {
835    return getClass().getSimpleName();
836  }
837}