/**
* 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.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import org.fudgemsg.FudgeContext;
import org.fudgemsg.FudgeMsgEnvelope;
import org.fudgemsg.wire.FudgeMsgReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.transport.FudgeMessageReceiver;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.TerminatableJob;
import com.opengamma.util.TerminatableJobContainer;
// REVIEW kirk 2010-05-12 -- Potential extensions in the future:
// - Use NIO for a large number of seldom broadcasting sockets
// - Allow for single-message receiving sockets that close themselves.
/**
* Listens on a server socket, receives Fudge-encoded messages, and hands them
* off to an underlying receiver for processing.
* An example use case here is a server process that receives messages from
* other nodes for asynchronous processing (such as a log aggregation server).
* <p/>
* This class will create one thread for each open external socket, as well as one
* thread to accept new sockets from the {@code ServerSocket}.
* <p>
* Each message will be handed to the underlying {@link FudgeMessageReceiver} in either
* the same thread as the messages are consumed (unless an executor service is
* supplied), so the underlying receiver must be threadsafe, and should not block except
* where it is fine to block the remote end from publishing during consumption.
*
* @author kirk
*/
public class ServerSocketFudgeMessageReceiver extends AbstractServerSocketProcess {
private static final Logger s_logger = LoggerFactory.getLogger(ServerSocketFudgeMessageReceiver.class);
private final FudgeMessageReceiver _underlying;
private final FudgeContext _context;
private final TerminatableJobContainer _messageReceiveJobs = new TerminatableJobContainer();
public ServerSocketFudgeMessageReceiver(final FudgeMessageReceiver underlying, final FudgeContext fudgeContext) {
ArgumentChecker.notNull(underlying, "underlying");
ArgumentChecker.notNull(fudgeContext, "fudgeContext");
_underlying = underlying;
_context = fudgeContext;
}
public ServerSocketFudgeMessageReceiver(final FudgeMessageReceiver underlying, final FudgeContext fudgeContext, final ExecutorService executorService) {
super(executorService);
ArgumentChecker.notNull(underlying, "underlying");
ArgumentChecker.notNull(fudgeContext, "fudgeContext");
_underlying = underlying;
_context = fudgeContext;
}
/**
* @return the underlying
*/
public FudgeMessageReceiver getUnderlying() {
return _underlying;
}
/**
* @return the context
*/
public FudgeContext getContext() {
return _context;
}
@Override
protected synchronized void socketOpened(Socket socket) {
ArgumentChecker.notNull(socket, "socket");
s_logger.info("Opened socket to remote side {}", socket.getRemoteSocketAddress());
InputStream is;
try {
is = socket.getInputStream();
} catch (IOException e) {
s_logger.warn("Unable to open InputStream for socket {}", new Object[]{socket}, e);
return;
}
is = new BufferedInputStream(is);
MessageReceiveJob job = new MessageReceiveJob(socket, is);
_messageReceiveJobs.addJobAndStartThread(job, "Message Receive " + socket.getRemoteSocketAddress());
}
@Override
protected void cleanupPreAccept() {
_messageReceiveJobs.cleanupTerminatedInstances();
}
private class MessageReceiveJob extends TerminatableJob {
private final Socket _socket;
private final FudgeMsgReader _reader;
// NOTE kirk 2010-05-12 -- Have to pass in the InputStream explicitly so that
// we can force the IOException catch up above.
public MessageReceiveJob(Socket socket, InputStream inputStream) {
ArgumentChecker.notNull(socket, "socket");
ArgumentChecker.notNull(inputStream, "inputStream");
_socket = socket;
_reader = _context.createMessageReader(inputStream);
}
@Override
protected void runOneCycle() {
if (_socket.isClosed()) {
terminate();
return;
}
final FudgeMsgEnvelope envelope;
try {
envelope = _reader.nextMessageEnvelope();
} catch (Exception e) {
s_logger.warn("Unable to read message from underlying stream", e);
return;
}
if (envelope == null) {
s_logger.info("Nothing available on the stream. Returning and terminating.");
terminate();
return;
}
final ExecutorService executorService = getExecutorService();
if (executorService != null) {
executorService.execute(new Runnable() {
@Override
public void run() {
dispatch(envelope);
}
});
} else {
dispatch(envelope);
}
}
private void dispatch(final FudgeMsgEnvelope envelope) {
try {
s_logger.debug("Received message with {} fields. Dispatching to underlying.", envelope.getMessage().getNumFields());
getUnderlying().messageReceived(getContext(), envelope);
} catch (Exception e) {
s_logger.warn("Unable to dispatch message to underlying receiver", e);
}
}
}
}