/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.transport;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.fudgemsg.FudgeContext;
import org.fudgemsg.FudgeMsg;
import org.fudgemsg.FudgeMsgEnvelope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.util.ArgumentChecker;
/**
* Allows synchronous RPC-style semantics to be applied over a {@link FudgeRequestSender}
* or {@link FudgeConnection}. This class also supports multiplexing different clients over
* the same underlying transport channel using correlation IDs to multiplex the requests
* and responses.
*/
public abstract class FudgeSynchronousClient implements FudgeMessageReceiver {
/** Logger. */
private static final Logger s_logger = LoggerFactory.getLogger(FudgeSynchronousClient.class);
/**
* The default timeout.
*/
private static final long DEFAULT_TIMEOUT_IN_MILLISECONDS = 30 * 1000L;
/**
* The generator of correlation ids.
*/
private final AtomicLong _nextCorrelationId = new AtomicLong();
/**
* The Fudge message sender.
*/
private final FudgeMessageSender _messageSender;
/**
* The map of pending requests keyed by correlation id.
*/
private final Map<Long, ClientRequestHolder> _pendingRequests = new ConcurrentHashMap<Long, ClientRequestHolder>();
/**
* The timeout.
*/
private long _timeoutInMilliseconds = DEFAULT_TIMEOUT_IN_MILLISECONDS;
/**
* Handler for asynchronous messages.
*/
private FudgeMessageReceiver _asynchronousMessageReceiver;
/**
* Creates the client.
* @param requestSender the sender, not null
*/
protected FudgeSynchronousClient(final FudgeRequestSender requestSender) {
ArgumentChecker.notNull(requestSender, "requestSender");
_messageSender = new FudgeMessageSender() {
@Override
public FudgeContext getFudgeContext() {
return requestSender.getFudgeContext();
}
@Override
public void send(FudgeMsg message) {
requestSender.sendRequest(message, FudgeSynchronousClient.this);
}
};
}
protected FudgeSynchronousClient(final FudgeConnection connection) {
ArgumentChecker.notNull(connection, "connection");
connection.setFudgeMessageReceiver(this);
_messageSender = connection.getFudgeMessageSender();
}
//-------------------------------------------------------------------------
/**
* Gets the message sender.
*
* @return the message sender, not null
*/
public FudgeMessageSender getMessageSender() {
return _messageSender;
}
/**
* Gets the timeout in milliseconds.
*
* @return the timeout
*/
public long getTimeoutInMilliseconds() {
return _timeoutInMilliseconds;
}
public void setTimeoutInMilliseconds(final long timeoutMilliseconds) {
_timeoutInMilliseconds = timeoutMilliseconds;
}
public void setAsynchronousMessageReceiver(final FudgeMessageReceiver asynchronousMessageReceiver) {
_asynchronousMessageReceiver = asynchronousMessageReceiver;
}
public FudgeMessageReceiver getAsynchronousMessageReceiver() {
return _asynchronousMessageReceiver;
}
/**
* Gets the next id.
*
* @return the next numeric id
*/
protected long getNextCorrelationId() {
return _nextCorrelationId.incrementAndGet();
}
//-------------------------------------------------------------------------
/**
* Sends the message.
*
* @param requestMsg the message, not null
* @param correlationId the message id
* @return the result
*/
protected FudgeMsg sendRequestAndWaitForResponse(FudgeMsg requestMsg, long correlationId) {
ClientRequestHolder requestHolder = new ClientRequestHolder();
_pendingRequests.put(correlationId, requestHolder);
try {
s_logger.debug("Sending message {}", correlationId);
getMessageSender().send(requestMsg);
try {
s_logger.debug("Blocking for message result");
requestHolder.latch.await(getTimeoutInMilliseconds(), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.interrupted();
s_logger.error("Interrupted");
}
if (requestHolder.resultValue == null) {
s_logger.warn("Didn't get response to {} in {}ms", correlationId, getTimeoutInMilliseconds());
throw new OpenGammaRuntimeException("Didn't receive a response message to " + correlationId + " in " + getTimeoutInMilliseconds() + "ms");
}
assert getCorrelationIdFromReply(requestHolder.resultValue) == correlationId;
s_logger.debug("Received result {}", requestHolder.resultValue);
return requestHolder.resultValue;
} finally {
_pendingRequests.remove(correlationId);
s_logger.debug("Request {} complete", correlationId);
}
}
protected void sendMessage(FudgeMsg message) {
getMessageSender().send(message);
}
/**
* Receives a message from Fudge.
*
* @param fudgeContext the Fudge context, not null
* @param msgEnvelope the message, not null
*/
@Override
public void messageReceived(FudgeContext fudgeContext, FudgeMsgEnvelope msgEnvelope) {
final FudgeMsg reply = msgEnvelope.getMessage();
final Long correlationId = getCorrelationIdFromReply(reply);
if (correlationId == null) {
final FudgeMessageReceiver receiver = getAsynchronousMessageReceiver();
if (receiver == null) {
s_logger.info("Unhandled asynchronous message {}", msgEnvelope);
} else {
receiver.messageReceived(fudgeContext, msgEnvelope);
}
return;
}
final ClientRequestHolder requestHolder = _pendingRequests.remove(correlationId);
if (requestHolder == null) {
s_logger.warn("Got a response on non-pending correlation Id {}", correlationId);
return;
}
requestHolder.resultValue = reply;
requestHolder.latch.countDown();
}
/**
* Extracts the correlation id from the reply object.
*
* @param reply the reply
* @return the id, null if it's an asynchronous message (over {@link FudgeConnection} transport only)
*/
protected abstract Long getCorrelationIdFromReply(FudgeMsg reply);
//-------------------------------------------------------------------------
/**
* Data holder.
*/
private static final class ClientRequestHolder {
public FudgeMsg resultValue; // CSIGNORE: simple holder object
public final CountDownLatch latch = new CountDownLatch(1); // CSIGNORE: simple holder object
}
}