/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.transport.socket;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import org.fudgemsg.FudgeContext;
import org.fudgemsg.FudgeMsg;
import org.fudgemsg.FudgeMsgEnvelope;
import org.fudgemsg.wire.FudgeMsgReader;
import org.fudgemsg.wire.FudgeRuntimeIOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.transport.FudgeConnection;
import com.opengamma.transport.FudgeConnectionReceiver;
import com.opengamma.transport.FudgeConnectionStateListener;
import com.opengamma.transport.FudgeMessageReceiver;
import com.opengamma.transport.FudgeMessageSender;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.TerminatableJob;
import com.opengamma.util.TerminatableJobContainer;
/**
* Listens on a ServerSocket and passes FudgeConnections to an underlying FudgeConnectionReceiver
*/
public class ServerSocketFudgeConnectionReceiver extends AbstractServerSocketProcess {
private static final Logger s_logger = LoggerFactory.getLogger(ServerSocketFudgeConnectionReceiver.class);
private final FudgeConnectionReceiver _underlying;
private final FudgeContext _fudgeContext;
private final TerminatableJobContainer _connectionJobs = new TerminatableJobContainer();
private boolean _lazyFudgeMsgReads;
public ServerSocketFudgeConnectionReceiver(final FudgeContext fudgeContext, final FudgeConnectionReceiver underlying) {
_fudgeContext = fudgeContext;
_underlying = underlying;
}
public ServerSocketFudgeConnectionReceiver(final FudgeContext fudgeContext, final FudgeConnectionReceiver underlying,
final ExecutorService executorService) {
super(executorService);
_fudgeContext = fudgeContext;
_underlying = underlying;
}
public FudgeContext getFudgeContext() {
return _fudgeContext;
}
public FudgeConnectionReceiver getUnderlying() {
return _underlying;
}
public void setLazyFudgeMsgReads(final boolean lazyFudgeMsgReads) {
_lazyFudgeMsgReads = lazyFudgeMsgReads;
}
public boolean isLazyFudgeMsgReads() {
return _lazyFudgeMsgReads;
}
@Override
protected void socketOpened(Socket socket) {
ArgumentChecker.notNull(socket, "socket");
s_logger.info("Opened socket to remote side {}", socket.getRemoteSocketAddress());
InputStream is;
OutputStream os;
try {
is = socket.getInputStream();
os = socket.getOutputStream();
} catch (IOException e) {
s_logger.warn("Unable to open InputStream and OutputStream for socket {}", new Object[] {socket}, e);
return;
}
final ConnectionJob job = new ConnectionJob(socket, is, os);
_connectionJobs.addJobAndStartThread(job, "Connection dispatch " + socket.getRemoteSocketAddress());
}
@Override
protected void cleanupPreAccept() {
_connectionJobs.cleanupTerminatedInstances();
}
@Override
public void stop() {
super.stop();
_connectionJobs.terminateAll();
}
/**
* An output stream with buffered behavior that only writes to the underlying stream when
* the buffer is full or when a flush takes place. This is different to the behavior of
* {@link BufferedOutputStream} which may make partial writes if given data larger than
* its internal buffer. This class is best used over network transports that have an optimal
* message size.
* <p>
* Note that it is not thread-safe.
*/
private static class StrictBufferedOutputStream extends FilterOutputStream {
// TODO: move this out into Util and use for other network buffered outputs
// TODO: set the packet size from the MTU details of the network used
private final byte[] _buffer;
private int _bytes;
public StrictBufferedOutputStream(final OutputStream out, final int bytes) {
super(out);
ArgumentChecker.isTrue(bytes > 0, "bytes");
_buffer = new byte[bytes];
}
public StrictBufferedOutputStream(final OutputStream out) {
this(out, 1500);
}
@Override
public void write(final int b) throws IOException {
_buffer[_bytes++] = (byte) b;
if (_bytes == _buffer.length) {
out.write(_buffer);
_bytes = 0;
}
}
@Override
public void write(final byte[] b, int ofs, int len) throws IOException {
if (_bytes > 0) {
final int room = _buffer.length - _bytes;
if (room < len) {
// Start of packet fits in buffer
System.arraycopy(b, ofs, _buffer, _bytes, room);
out.write(_buffer);
_bytes = 0;
ofs += room;
len -= room;
} else {
System.arraycopy(b, ofs, _buffer, _bytes, len);
if (room == len) {
// Packet just fits in buffer
System.arraycopy(b, ofs, _buffer, _bytes, len);
out.write(_buffer);
_bytes = 0;
} else {
// Packet fits entirely in buffer
_bytes += len;
}
return;
}
}
while (len >= _buffer.length) {
// Part of the packet can be sent directly
out.write(b, ofs, _buffer.length);
ofs += _buffer.length;
len -= _buffer.length;
}
if (len > 0) {
// Part of the packet is left
System.arraycopy(b, ofs, _buffer, _bytes, len);
_bytes += len;
}
}
@Override
public void flush() throws IOException {
if (_bytes > 0) {
out.write(_buffer, 0, _bytes);
_bytes = 0;
out.flush();
}
}
}
private class ConnectionJob extends TerminatableJob {
private final Socket _socket;
private final FudgeMsgReader _reader;
private final FudgeMessageSender _sender;
private final FudgeConnection _connection;
private FudgeMessageReceiver _receiver;
private volatile FudgeConnectionStateListener _listener;
ConnectionJob(final Socket socket, final InputStream is, final OutputStream os) {
_socket = socket;
_reader = getFudgeContext().createMessageReader(new BufferedInputStream(is));
_reader.setLazyReads(isLazyFudgeMsgReads());
_sender = new FudgeMessageSender() {
private final MessageBatchingWriter _writer = new MessageBatchingWriter(getFudgeContext(),
new StrictBufferedOutputStream(os));
@Override
public FudgeContext getFudgeContext() {
return ServerSocketFudgeConnectionReceiver.this.getFudgeContext();
}
@Override
public void send(FudgeMsg message) {
try {
_writer.write(message);
} catch (FudgeRuntimeIOException e) {
terminateWithError("Unable to write message to underlying stream - terminating connection", e.getCause());
throw e;
}
}
@Override
public String toString() {
return _socket.getRemoteSocketAddress().toString();
}
};
_connection = new FudgeConnection() {
@Override
public FudgeMessageSender getFudgeMessageSender() {
return _sender;
}
@Override
public void setFudgeMessageReceiver(FudgeMessageReceiver receiver) {
_receiver = receiver;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("FudgeConnection from ");
sb.append(_socket.getRemoteSocketAddress().toString());
return sb.toString();
}
@Override
public void setConnectionStateListener(final FudgeConnectionStateListener listener) {
_listener = listener;
}
};
}
@Override
protected void runOneCycle() {
if (_socket.isClosed()) {
terminate();
return;
}
final FudgeMsgEnvelope envelope;
try {
envelope = _reader.nextMessageEnvelope();
} catch (FudgeRuntimeIOException e) {
terminateWithError("Unable to read message from underlying stream - terminating connection", e.getCause());
return;
}
if (envelope == null) {
terminateWithError("Nothing available on stream - terminating connection", null);
return;
}
final FudgeMessageReceiver receiver = _receiver;
if (receiver != null) {
final ExecutorService executorService = getExecutorService();
if (executorService != null) {
executorService.execute(new Runnable() {
@Override
public void run() {
dispatchReceiver(receiver, envelope);
}
});
} else {
dispatchReceiver(receiver, envelope);
}
} else {
try {
getUnderlying().connectionReceived(getFudgeContext(), envelope, _connection);
} catch (Exception e) {
s_logger.warn("Unable to dispatch connection to receiver", e);
}
}
}
private void dispatchReceiver(final FudgeMessageReceiver receiver, final FudgeMsgEnvelope envelope) {
try {
receiver.messageReceived(getFudgeContext(), envelope);
} catch (Exception e) {
s_logger.warn("Unable to dispatch message to receiver", e);
}
}
private void terminateWithError(final String errorMessage, final Exception cause) {
if (cause != null) {
if (exceptionForcedByClose(cause)) {
s_logger.info("Connection terminated");
} else {
s_logger.warn(errorMessage, cause);
terminate();
}
} else {
s_logger.info(errorMessage);
terminate();
}
final FudgeConnectionStateListener listener = _listener;
if (listener != null) {
listener.connectionFailed(_connection, cause);
}
}
@Override
public void terminate() {
if (!_socket.isClosed()) {
try {
s_logger.debug("Closing socket");
_socket.close();
} catch (IOException ex) {
s_logger.warn("Couldn't close socket to release blocked I/O", ex.getMessage());
}
}
super.terminate();
}
}
}