InternalLDAPOutputStream.java revision ea1068c292e9b341af6d6b563cd8988a96be20a9
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2009 Sun Microsystems, Inc.
* Portions Copyright 2014-2015 ForgeRock AS
*/
package org.opends.server.protocols.internal;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.io.ASN1;
import org.forgerock.opendj.io.ASN1Reader;
import org.forgerock.opendj.ldap.ByteSequenceReader;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ByteStringBuilder;
import org.opends.server.core.*;
import org.opends.server.protocols.ldap.*;
import org.opends.server.types.*;
import static org.forgerock.opendj.ldap.DecodeException.*;
import static org.opends.messages.ProtocolMessages.*;
import static org.opends.server.protocols.internal.InternalClientConnection.*;
import static org.opends.server.protocols.internal.Requests.*;
import static org.opends.server.protocols.ldap.LDAPConstants.*;
import static org.opends.server.util.ServerConstants.*;
/**
* This class provides an implementation of a
* {@code java.io.OutputStream} that can be used to facilitate
* internal communication with the Directory Server. On the backend,
* data written to this output stream will be first decoded as an
* ASN.1 element and then as an LDAP message. That LDAP message will
* be converted to an internal operation which will then be processed
* and the result returned to the client via the input stream on the
* other side of the associated internal LDAP socket.
*/
@org.opends.server.types.PublicAPI(
stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
mayInstantiate=false,
mayExtend=false,
mayInvoke=true)
public final class InternalLDAPOutputStream
extends OutputStream
implements InternalSearchListener
{
// Indicates whether this stream has been closed.
private boolean closed;
private final ASN1Reader reader;
// The internal LDAP socket with which this output stream is
// associated.
private final InternalLDAPSocket socket;
// The immediate data being written.
private ByteSequenceReader byteBuffer;
// The save buffer used to store any unprocessed data waiting
// to be read as ASN.1 elements. (Usually due to writing incomplete
// ASN.1 elements.)
private final ByteStringBuilder saveBuffer;
private final ByteSequenceReader saveBufferReader;
/**
* An adaptor class for reading from a save buffer and the bytes
* being written sequentially using the InputStream interface.
*
* Since the bytes being written are only available duing the write
* call, any unused data will be appended to the save buffer before
* returning from the write method. This reader will always read the
* save buffer first before the actual bytes being written to ensure
* bytes are read in the same order as they are written.
*/
private class CombinedBufferInputStream extends InputStream
{
/**
* {@inheritDoc}
*/
@Override
public int available()
{
// The number of available bytes is the sum of the save buffer
// and the last read data in the NIO ByteStringBuilder.
return saveBufferReader.remaining() + byteBuffer.remaining();
}
/**
* {@inheritDoc}
*/
@Override
public int read()
{
if(saveBufferReader.remaining() > 0)
{
// Try saved buffer first
return 0xFF & saveBufferReader.get();
}
if(byteBuffer.remaining() > 0)
{
// Must still be on the channel buffer
return 0xFF & byteBuffer.get();
}
return -1;
}
/**
* {@inheritDoc}
*/
@Override
public int read(byte[] bytes)
{
return read(bytes, 0, bytes.length);
}
/**
* {@inheritDoc}
*/
@Override
public int read(byte[] value, int off, int length)
{
int bytesCopied=0;
int len;
if(saveBufferReader.remaining() > 0)
{
// Copy out of the last saved buffer first
len = Math.min(saveBufferReader.remaining(), length);
saveBufferReader.get(value, off, len);
bytesCopied += len;
}
if(bytesCopied < length && byteBuffer.remaining() > 0)
{
// Copy out of the channel buffer if we haven't got
// everything we needed.
len = Math.min(byteBuffer.remaining(), length - bytesCopied);
byteBuffer.get(value, off + bytesCopied, len);
bytesCopied += len;
}
return bytesCopied;
}
/**
* {@inheritDoc}
*/
@Override
public long skip(long length)
{
int bytesSkipped=0;
int len;
if(saveBufferReader.remaining() > 0)
{
// Skip in the last saved buffer first
len = Math.min(saveBufferReader.remaining(), (int)length);
saveBufferReader.position(saveBufferReader.position() + len);
bytesSkipped += len;
}
if(bytesSkipped < length && byteBuffer.remaining() > 0)
{
//Skip in the channel buffer if we haven't skipped enough.
len = Math.min(byteBuffer.remaining(),
(int)length - bytesSkipped);
byteBuffer.position(byteBuffer.position() + len);
bytesSkipped += len;
}
return bytesSkipped;
}
}
/**
* Creates a new instance of an internal LDAP output stream that is
* associated with the provided internal LDAP socket.
*
* @param socket The internal LDAP socket that will be serviced by
* this internal LDAP output stream.
*/
public InternalLDAPOutputStream(InternalLDAPSocket socket)
{
this.socket = socket;
this.closed = false;
this.saveBuffer = new ByteStringBuilder();
this.saveBufferReader = saveBuffer.asReader();
CombinedBufferInputStream bufferStream =
new CombinedBufferInputStream();
this.reader = ASN1.getReader(bufferStream);
}
/**
* Closes this output stream, its associated socket, and the
* socket's associated input stream.
*/
@Override
public void close()
{
socket.close();
}
/**
* Closes this output stream through an internal mechanism that will
* not cause an infinite recursion loop by trying to also close the
* output stream.
*/
@org.opends.server.types.PublicAPI(
stability=org.opends.server.types.StabilityLevel.PRIVATE,
mayInstantiate=false,
mayExtend=false,
mayInvoke=false)
void closeInternal()
{
closed = true;
}
/**
* Flushes this output stream and forces any buffered data to be
* written out. This will have no effect, since this output
* stream implementation does not use buffering.
*/
@Override
public void flush()
{
// No implementation is required.
}
/**
* Writes the contents of the provided byte array to this output
* stream.
*
* @param b The byte array to be written.
*
* @throws IOException If the output stream is closed, or if there
* is a problem with the data being written.
*/
@Override
public void write(byte[] b)
throws IOException
{
write(b, 0, b.length);
}
/**
* Writes the specified portion of the data in the provided byte
* array to this output stream. Any data written will be
* accumulated until a complete ASN.1 element is available, at which
* point it will be decoded as an LDAP message and converted to an
* internal operation that will be processed.
*
* @param b The byte array containing the data to be read.
* @param off The position in the array at which to start reading
* data.
* @param len The number of bytes to read from the array.
*
* @throws IOException If the output stream is closed, or if there
* is a problem with the data being written.
*/
@Override
public synchronized void write(byte[] b, int off, int len)
throws IOException
{
if (closed)
{
LocalizableMessage m = ERR_INTERNALOS_CLOSED.get();
throw new IOException(m.toString());
}
byteBuffer = ByteString.wrap(b, off, len).asReader();
try
{
while(reader.elementAvailable())
{
LDAPMessage msg = LDAPReader.readMessage(reader);
processMessage(msg);
}
}
catch(Exception e)
{
throw new IOException(e.getMessage());
}
// Clear the save buffer if we have read all of it
if(saveBufferReader.remaining() == 0)
{
saveBuffer.clear();
saveBufferReader.rewind();
}
// Append any unused data in the channel buffer to the save buffer
if(byteBuffer.remaining() > 0)
{
saveBuffer.append(byteBuffer, byteBuffer.remaining());
}
}
/**
* Writes a single byte of data to this output stream. If the byte
* written completes an ASN.1 element that was in progress, then it
* will be decoded as an LDAP message and converted to an internal
* operation that will be processed. Otherwise, the data will be
* accumulated until a complete element can be formed.
*
* @param b The byte to be written.
*
* @throws IOException If the output stream is closed, or if there
* is a problem with the data being written.
*/
@Override
public synchronized void write(int b)
throws IOException
{
write(new byte[]{(byte)b}, 0, 1);
}
/**
* Processes the provided ASN.1 element by decoding it as an LDAP
* message, converting that to an internal operation, and sending
* the appropriate response message(s) to the client through the
* corresponding internal LDAP input stream.
*
* @param message The LDAP message to process.
*
* @throws IOException If a problem occurs while attempting to
* decode the provided ASN.1 element as an
* LDAP message.
*/
private void processMessage(LDAPMessage message)
throws IOException
{
switch (message.getProtocolOpType())
{
case OP_TYPE_ABANDON_REQUEST:
// No action is required.
return;
case OP_TYPE_ADD_REQUEST:
processAddOperation(message);
break;
case OP_TYPE_BIND_REQUEST:
processBindOperation(message);
break;
case OP_TYPE_COMPARE_REQUEST:
processCompareOperation(message);
break;
case OP_TYPE_DELETE_REQUEST:
processDeleteOperation(message);
break;
case OP_TYPE_EXTENDED_REQUEST:
processExtendedOperation(message);
break;
case OP_TYPE_MODIFY_REQUEST:
processModifyOperation(message);
break;
case OP_TYPE_MODIFY_DN_REQUEST:
processModifyDNOperation(message);
break;
case OP_TYPE_SEARCH_REQUEST:
processSearchOperation(message);
break;
case OP_TYPE_UNBIND_REQUEST:
socket.close();
break;
default:
LocalizableMessage m = ERR_INTERNALOS_INVALID_REQUEST.get(
message.getProtocolElementName());
throw new IOException(m.toString());
}
}
/**
* Processes the content of the provided LDAP message as an add
* operation and returns the appropriate result to the client.
*
* @param message The LDAP message containing the request to
* process.
*
* @throws IOException If a problem occurs while attempting to
* process the operation.
*/
private void processAddOperation(LDAPMessage message)
throws IOException
{
int messageID = message.getMessageID();
AddRequestProtocolOp request = message.getAddRequestProtocolOp();
InternalClientConnection conn = socket.getConnection();
AddOperationBasis op =
new AddOperationBasis(conn, InternalClientConnection.nextOperationID(),
messageID, message.getControls(),
request.getDN(),
request.getAttributes());
op.run();
AddResponseProtocolOp addResponse =
new AddResponseProtocolOp(op.getResultCode().intValue(),
op.getErrorMessage().toMessage(),
op.getMatchedDN(),
op.getReferralURLs());
List<Control> responseControls = op.getResponseControls();
socket.getInputStream().addLDAPMessage(
new LDAPMessage(messageID, addResponse, responseControls));
}
/**
* Processes the content of the provided LDAP message as a bind
* operation and returns the appropriate result to the client.
*
* @param message The LDAP message containing the request to
* process.
*
* @throws IOException If a problem occurs while attempting to
* process the operation.
*/
private void processBindOperation(LDAPMessage message)
throws IOException
{
int messageID = message.getMessageID();
BindRequestProtocolOp request =
message.getBindRequestProtocolOp();
if (request.getAuthenticationType() == AuthenticationType.SASL)
{
LocalizableMessage m = ERR_INTERNALOS_SASL_BIND_NOT_SUPPORTED.get();
BindResponseProtocolOp bindResponse =
new BindResponseProtocolOp(
LDAPResultCode.UNWILLING_TO_PERFORM, m);
socket.getInputStream().addLDAPMessage(
new LDAPMessage(messageID, bindResponse));
return;
}
InternalClientConnection conn = socket.getConnection();
BindOperationBasis op =
new BindOperationBasis(conn, nextOperationID(),
messageID, message.getControls(),
String.valueOf(request.getProtocolVersion()),
request.getDN(), request.getSimplePassword());
op.run();
BindResponseProtocolOp bindResponse =
new BindResponseProtocolOp(op.getResultCode().intValue(),
op.getErrorMessage().toMessage(),
op.getMatchedDN(),
op.getReferralURLs());
List<Control> responseControls = op.getResponseControls();
if (bindResponse.getResultCode() == LDAPResultCode.SUCCESS)
{
socket.setConnection(new InternalClientConnection(
op.getAuthenticationInfo()));
}
socket.getInputStream().addLDAPMessage(
new LDAPMessage(messageID, bindResponse, responseControls));
}
/**
* Processes the content of the provided LDAP message as a compare
* operation and returns the appropriate result to the client.
*
* @param message The LDAP message containing the request to
* process.
*
* @throws IOException If a problem occurs while attempting to
* process the operation.
*/
private void processCompareOperation(LDAPMessage message)
throws IOException
{
int messageID = message.getMessageID();
CompareRequestProtocolOp request =
message.getCompareRequestProtocolOp();
InternalClientConnection conn = socket.getConnection();
CompareOperationBasis op =
new CompareOperationBasis(conn, nextOperationID(),
messageID, message.getControls(), request.getDN(),
request.getAttributeType(),
request.getAssertionValue());
op.run();
CompareResponseProtocolOp compareResponse =
new CompareResponseProtocolOp(
op.getResultCode().intValue(),
op.getErrorMessage().toMessage(),
op.getMatchedDN(),
op.getReferralURLs());
List<Control> responseControls = op.getResponseControls();
socket.getInputStream().addLDAPMessage(
new LDAPMessage(messageID, compareResponse,
responseControls));
}
/**
* Processes the content of the provided LDAP message as a delete
* operation and returns the appropriate result to the client.
*
* @param message The LDAP message containing the request to
* process.
*
* @throws IOException If a problem occurs while attempting to
* process the operation.
*/
private void processDeleteOperation(LDAPMessage message)
throws IOException
{
int messageID = message.getMessageID();
DeleteRequestProtocolOp request =
message.getDeleteRequestProtocolOp();
InternalClientConnection conn = socket.getConnection();
DeleteOperationBasis op =
new DeleteOperationBasis(conn, nextOperationID(),
messageID, message.getControls(), request.getDN());
op.run();
DeleteResponseProtocolOp deleteResponse =
new DeleteResponseProtocolOp(
op.getResultCode().intValue(),
op.getErrorMessage().toMessage(),
op.getMatchedDN(),
op.getReferralURLs());
List<Control> responseControls = op.getResponseControls();
socket.getInputStream().addLDAPMessage(
new LDAPMessage(messageID, deleteResponse,
responseControls));
}
/**
* Processes the content of the provided LDAP message as an extended
* operation and returns the appropriate result to the client.
*
* @param message The LDAP message containing the request to
* process.
*
* @throws IOException If a problem occurs while attempting to
* process the operation.
*/
private void processExtendedOperation(LDAPMessage message)
throws IOException
{
int messageID = message.getMessageID();
ExtendedRequestProtocolOp request =
message.getExtendedRequestProtocolOp();
if (request.getOID().equals(OID_START_TLS_REQUEST))
{
LocalizableMessage m = ERR_INTERNALOS_STARTTLS_NOT_SUPPORTED.get();
ExtendedResponseProtocolOp extendedResponse =
new ExtendedResponseProtocolOp(
LDAPResultCode.UNWILLING_TO_PERFORM, m);
socket.getInputStream().addLDAPMessage(
new LDAPMessage(messageID, extendedResponse));
return;
}
InternalClientConnection conn = socket.getConnection();
ExtendedOperationBasis op =
new ExtendedOperationBasis(conn, nextOperationID(),
messageID, message.getControls(), request.getOID(),
request.getValue());
op.run();
ExtendedResponseProtocolOp extendedResponse =
new ExtendedResponseProtocolOp(
op.getResultCode().intValue(),
op.getErrorMessage().toMessage(),
op.getMatchedDN(),
op.getReferralURLs(), op.getResponseOID(),
op.getResponseValue());
List<Control> responseControls = op.getResponseControls();
socket.getInputStream().addLDAPMessage(
new LDAPMessage(messageID, extendedResponse,
responseControls));
}
/**
* Processes the content of the provided LDAP message as a modify
* operation and returns the appropriate result to the client.
*
* @param message The LDAP message containing the request to
* process.
*
* @throws IOException If a problem occurs while attempting to
* process the operation.
*/
private void processModifyOperation(LDAPMessage message)
throws IOException
{
int messageID = message.getMessageID();
ModifyRequestProtocolOp request =
message.getModifyRequestProtocolOp();
InternalClientConnection conn = socket.getConnection();
ModifyOperationBasis op =
new ModifyOperationBasis(conn, nextOperationID(),
messageID, message.getControls(), request.getDN(),
request.getModifications());
op.run();
ModifyResponseProtocolOp modifyResponse =
new ModifyResponseProtocolOp(
op.getResultCode().intValue(),
op.getErrorMessage().toMessage(),
op.getMatchedDN(),
op.getReferralURLs());
List<Control> responseControls = op.getResponseControls();
socket.getInputStream().addLDAPMessage(
new LDAPMessage(messageID, modifyResponse,
responseControls));
}
/**
* Processes the content of the provided LDAP message as a modify DN
* operation and returns the appropriate result to the client.
*
* @param message The LDAP message containing the request to
* process.
*
* @throws IOException If a problem occurs while attempting to
* process the operation.
*/
private void processModifyDNOperation(LDAPMessage message)
throws IOException
{
int messageID = message.getMessageID();
ModifyDNRequestProtocolOp request =
message.getModifyDNRequestProtocolOp();
InternalClientConnection conn = socket.getConnection();
ModifyDNOperationBasis op =
new ModifyDNOperationBasis(conn, nextOperationID(),
messageID, message.getControls(), request.getEntryDN(),
request.getNewRDN(), request.deleteOldRDN(),
request.getNewSuperior());
op.run();
ModifyDNResponseProtocolOp modifyDNResponse =
new ModifyDNResponseProtocolOp(
op.getResultCode().intValue(),
op.getErrorMessage().toMessage(),
op.getMatchedDN(),
op.getReferralURLs());
List<Control> responseControls = op.getResponseControls();
socket.getInputStream().addLDAPMessage(
new LDAPMessage(messageID, modifyDNResponse,
responseControls));
}
/**
* Processes the content of the provided LDAP message as a search
* operation and returns the appropriate result to the client.
*
* @param message The LDAP message containing the request to
* process.
*
* @throws IOException If a problem occurs while attempting to
* process the operation.
*/
private void processSearchOperation(LDAPMessage message)
throws IOException
{
int messageID = message.getMessageID();
SearchRequestProtocolOp request = message.getSearchRequestProtocolOp();
InternalClientConnection conn = socket.getConnection();
DN baseDN = null;
SearchFilter filter;
try
{
baseDN = DN.valueOf(request.getBaseDN().toString());
filter = request.getFilter().toSearchFilter();
}
catch (DirectoryException e)
{
final String cause = (baseDN == null ? "baseDN" : "filter");
throw error(LocalizableMessage.raw("Could not decode " + cause), e);
}
SearchRequest sr = newSearchRequest(baseDN, request.getScope(), filter)
.setDereferenceAliasesPolicy(request.getDereferencePolicy())
.setSizeLimit(request.getSizeLimit())
.setTimeLimit(request.getTimeLimit())
.setTypesOnly(request.getTypesOnly())
.addAttribute(request.getAttributes())
.addControl(message.getControls());
InternalSearchOperation op = new InternalSearchOperation(
conn, nextOperationID(), messageID, sr, this);
op.run();
SearchResultDoneProtocolOp searchDone =
new SearchResultDoneProtocolOp(
op.getResultCode().intValue(),
op.getErrorMessage().toMessage(),
op.getMatchedDN(),
op.getReferralURLs());
List<Control> responseControls = op.getResponseControls();
socket.getInputStream().addLDAPMessage(
new LDAPMessage(messageID, searchDone, responseControls));
}
/**
* Performs any processing necessary for the provided search result
* entry.
*
* @param searchOperation The internal search operation being
* processed.
* @param searchEntry The matching search result entry to be
* processed.
*/
@Override
@org.opends.server.types.PublicAPI(
stability=org.opends.server.types.StabilityLevel.PRIVATE,
mayInstantiate=false,
mayExtend=false,
mayInvoke=false)
public void handleInternalSearchEntry(
InternalSearchOperation searchOperation,
SearchResultEntry searchEntry)
{
List<Control> entryControls = searchEntry.getControls();
SearchResultEntryProtocolOp entry =
new SearchResultEntryProtocolOp(searchEntry);
socket.getInputStream().addLDAPMessage(
new LDAPMessage(searchOperation.getMessageID(), entry,
entryControls));
}
/**
* Performs any processing necessary for the provided search result
* reference.
*
* @param searchOperation The internal search operation being
* processed.
* @param searchReference The search result reference to be
* processed.
*/
@Override
@org.opends.server.types.PublicAPI(
stability=org.opends.server.types.StabilityLevel.PRIVATE,
mayInstantiate=false,
mayExtend=false,
mayInvoke=false)
public void handleInternalSearchReference(
InternalSearchOperation searchOperation,
SearchResultReference searchReference)
{
List<Control> entryControls = searchReference.getControls();
SearchResultReferenceProtocolOp reference =
new SearchResultReferenceProtocolOp(searchReference);
socket.getInputStream().addLDAPMessage(
new LDAPMessage(searchOperation.getMessageID(), reference,
entryControls));
}
/**
* Retrieves a string representation of this internal LDAP socket.
*
* @return A string representation of this internal LDAP socket.
*/
@Override
public String toString()
{
return "InternalLDAPOutputStream";
}
}