package org.marketcetera.marketdata.core.rpc;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import org.marketcetera.core.ApplicationVersion;
import org.marketcetera.core.CloseableLock;
import org.marketcetera.core.Util;
import org.marketcetera.core.VersionInfo;
import org.marketcetera.core.notifications.ServerStatusListener;
import org.marketcetera.core.publisher.ISubscriber;
import org.marketcetera.core.publisher.PublisherEngine;
import org.marketcetera.event.Event;
import org.marketcetera.marketdata.Capability;
import org.marketcetera.marketdata.Content;
import org.marketcetera.marketdata.MarketDataRequest;
import org.marketcetera.marketdata.core.Messages;
import org.marketcetera.marketdata.core.manager.MarketDataRequestFailed;
import org.marketcetera.marketdata.core.manager.NoMarketDataProvidersAvailable;
import org.marketcetera.marketdata.core.rpc.RpcMarketdata.Locale;
import org.marketcetera.marketdata.core.rpc.RpcMarketdata.LoginRequest;
import org.marketcetera.marketdata.core.rpc.RpcMarketdata.LoginResponse;
import org.marketcetera.marketdata.core.rpc.RpcMarketdata.LogoutRequest;
import org.marketcetera.marketdata.core.rpc.RpcMarketdata.RpcMarketDataService;
import org.marketcetera.marketdata.core.rpc.RpcMarketdata.RpcMarketDataService.BlockingInterface;
import org.marketcetera.marketdata.core.webservice.ConnectionException;
import org.marketcetera.marketdata.core.webservice.MarketDataServiceClient;
import org.marketcetera.marketdata.core.webservice.PageRequest;
import org.marketcetera.trade.Instrument;
import org.marketcetera.util.log.I18NBoundMessage1P;
import org.marketcetera.util.log.SLF4JLoggerProxy;
import org.marketcetera.util.misc.ClassVersion;
import org.marketcetera.util.ws.ContextClassProvider;
import org.marketcetera.util.ws.tags.AppId;
import org.marketcetera.util.ws.tags.NodeId;
import org.marketcetera.util.ws.tags.SessionId;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.protobuf.RpcController;
import com.google.protobuf.ServiceException;
import com.googlecode.protobuf.pro.duplex.PeerInfo;
import com.googlecode.protobuf.pro.duplex.RpcClientChannel;
import com.googlecode.protobuf.pro.duplex.client.DuplexTcpClientPipelineFactory;
import com.googlecode.protobuf.pro.duplex.execute.RpcServerCallExecutor;
import com.googlecode.protobuf.pro.duplex.execute.ThreadPoolCallExecutor;
/* $License$ */
/**
* Provides market data services via RPC.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: MarketDataRpcClient.java 16901 2014-05-11 16:14:11Z colin $
* @since 2.4.0
*/
@ThreadSafe
@ClassVersion("$Id: MarketDataRpcClient.java 16901 2014-05-11 16:14:11Z colin $")
public class MarketDataRpcClient
implements MarketDataServiceClient
{
/**
* Create a new MarketDataServiceRpcClient instance.
*
* @param inUsername a <code>String</code> value
* @param inPassword a <code>String</code> value
* @param inHostname a <code>String</code> value
* @param inPort an <code>int</code> value
* @param inContextClassProvider a <code>ContextClassProvider</code> value
*/
public MarketDataRpcClient(String inUsername,
String inPassword,
String inHostname,
int inPort,
ContextClassProvider inContextClassProvider)
{
username = inUsername;
password = inPassword;
hostname = inHostname;
port = inPort;
contextClassProvider = inContextClassProvider;
}
/* (non-Javadoc)
* @see org.springframework.context.Lifecycle#start()
*/
@Override
public synchronized void start()
{
shutdownRequested.set(false);
try {
synchronized(contextLock) {
context = JAXBContext.newInstance(contextClassProvider==null?new Class<?>[0]:contextClassProvider.getContextClasses());
marshaller = context.createMarshaller();
unmarshaller = context.createUnmarshaller();
}
startService();
heartbeatFuture = heartbeatService.scheduleAtFixedRate(new HeartbeatMonitor(),
heartbeatInterval,
heartbeatInterval,
TimeUnit.MILLISECONDS);
} catch (IOException | ServiceException | JAXBException e) {
throw new RuntimeException(e);
}
}
/* (non-Javadoc)
* @see org.springframework.context.Lifecycle#stop()
*/
@Override
public synchronized void stop()
{
shutdownRequested.set(true);
try {
if(heartbeatFuture != null) {
try {
heartbeatFuture.cancel(true);
} catch (Exception ignored) {}
}
try {
stopService();
} catch (Exception ignored) {}
} finally {
heartbeatFuture = null;
}
}
/* (non-Javadoc)
* @see org.springframework.context.Lifecycle#isRunning()
*/
@Override
public boolean isRunning()
{
return running.get();
}
/* (non-Javadoc)
* @see org.marketcetera.marketdata.core.webservice.MarketDataServiceClient#request(org.marketcetera.marketdata.MarketDataRequest, boolean)
*/
@Override
public long request(MarketDataRequest inRequest,
boolean inStreamEvents)
{
SLF4JLoggerProxy.debug(this,
"MarketDataRequest: {}", //$NON-NLS-1$
inRequest);
try(CloseableLock requestLock = CloseableLock.create(serviceLock.readLock())) {
requestLock.lock();
RpcMarketdata.MarketDataResponse response = clientService.request(controller,
RpcMarketdata.MarketDataRequest.newBuilder().setSessionId(sessionId.getValue())
.setRequest(inRequest.toString())
.setStreamEvents(inStreamEvents).build());
SLF4JLoggerProxy.debug(this,
"MarketDataResponse: {}", //$NON-NLS-1$
response.getId());
validateResponse(response.getFailed(),
response.getMessage());
return response.getId();
} catch (ServiceException e) {
throw new ConnectionException(e);
}
}
/* (non-Javadoc)
* @see org.marketcetera.marketdata.core.webservice.MarketDataServiceClient#getLastUpdate(long)
*/
@Override
public long getLastUpdate(long inRequestId)
{
SLF4JLoggerProxy.debug(this,
"GetLastUpdate: {}", //$NON-NLS-1$
inRequestId);
try(CloseableLock requestLock = CloseableLock.create(serviceLock.readLock())) {
requestLock.lock();
RpcMarketdata.LastUpdateResponse response = clientService.getLastUpdate(controller,
RpcMarketdata.LastUpdateRequest.newBuilder().setSessionId(sessionId.getValue())
.setId(inRequestId).build());
SLF4JLoggerProxy.debug(this,
"GetLastUpdateResponse: {}", //$NON-NLS-1$
response.getTimestamp());
return response.getTimestamp();
} catch (ServiceException e) {
throw new ConnectionException(e);
}
}
/* (non-Javadoc)
* @see org.marketcetera.marketdata.core.webservice.MarketDataServiceClient#cancel(long)
*/
@Override
public void cancel(long inRequestId)
{
SLF4JLoggerProxy.debug(this,
"Cancel: {}", //$NON-NLS-1$
inRequestId);
try(CloseableLock requestLock = CloseableLock.create(serviceLock.readLock())) {
requestLock.lock();
RpcMarketdata.CancelResponse response = clientService.cancel(controller,
RpcMarketdata.CancelRequest.newBuilder().setSessionId(sessionId.getValue())
.setId(inRequestId).build());
SLF4JLoggerProxy.debug(this,
"Cancel Response: {}", //$NON-NLS-1$
response);
return;
} catch (ServiceException e) {
throw new ConnectionException(e);
}
}
/* (non-Javadoc)
* @see org.marketcetera.marketdata.core.webservice.MarketDataServiceClient#getEvents(long)
*/
@Override
public Deque<Event> getEvents(long inRequestId)
{
SLF4JLoggerProxy.debug(this,
"GetEvents: {}", //$NON-NLS-1$
inRequestId);
try(CloseableLock requestLock = CloseableLock.create(serviceLock.readLock())) {
requestLock.lock();
RpcMarketdata.EventsResponse response = clientService.getEvents(controller,
RpcMarketdata.EventsRequest.newBuilder().setSessionId(sessionId.getValue())
.setId(inRequestId).build());
Deque<Event> events = Lists.newLinkedList();
for(String payload : response.getPayloadList()) {
events.add((Event)unmarshall(payload));
}
SLF4JLoggerProxy.debug(this,
"GetEventsResponse: {}", //$NON-NLS-1$
events);
return events;
} catch (ServiceException | JAXBException e) {
throw new ConnectionException(e);
}
}
/* (non-Javadoc)
* @see org.marketcetera.marketdata.core.webservice.MarketDataServiceClient#getAllEvents(java.util.List)
*/
@Override
public Map<Long,LinkedList<Event>> getAllEvents(List<Long> inRequestIds)
{
SLF4JLoggerProxy.debug(this,
"GetAllEvents: {}", //$NON-NLS-1$
inRequestIds);
try(CloseableLock requestLock = CloseableLock.create(serviceLock.readLock())) {
requestLock.lock();
RpcMarketdata.AllEventsResponse response = clientService.getAllEvents(controller,
RpcMarketdata.AllEventsRequest.newBuilder().setSessionId(sessionId.getValue())
.addAllId(inRequestIds).build());
Map<Long,LinkedList<Event>> events = Maps.newHashMap();
for(RpcMarketdata.EventsResponse eventResponse : response.getEventsList()) {
LinkedList<Event> eventList = new LinkedList<>();
for(String payload : eventResponse.getPayloadList()) {
eventList.add((Event)unmarshall(payload));
}
events.put(eventResponse.getId(),
eventList);
}
SLF4JLoggerProxy.debug(this,
"GetAllEventsResponse: {}", //$NON-NLS-1$
events);
return events;
} catch (ServiceException | JAXBException e) {
throw new ConnectionException(e);
}
}
/* (non-Javadoc)
* @see org.marketcetera.marketdata.core.webservice.MarketDataServiceClient#getSnapshot(org.marketcetera.trade.Instrument, org.marketcetera.marketdata.Content, java.lang.String)
*/
@Override
public Deque<Event> getSnapshot(Instrument inInstrument,
Content inContent,
String inProvider)
{
SLF4JLoggerProxy.debug(this,
"GetSnapshot: {}/{}/{}", //$NON-NLS-1$
inInstrument,
inContent,
inProvider);
try(CloseableLock requestLock = CloseableLock.create(serviceLock.readLock())) {
requestLock.lock();
RpcMarketdata.SnapshotRequest.Builder requestBuilder = RpcMarketdata.SnapshotRequest.newBuilder().setSessionId(sessionId.getValue());
requestBuilder.setContent(RpcMarketdata.ContentAndCapability.valueOf(inContent.name()))
.setInstrument(RpcMarketdata.Instrument.newBuilder().setPayload(marshall(inInstrument)));
if(inProvider != null){
requestBuilder.setProvider(inProvider);
}
RpcMarketdata.SnapshotResponse response = clientService.getSnapshot(controller,
requestBuilder.build());
Deque<Event> events = Lists.newLinkedList();
for(String payload : response.getPayloadList()) {
events.add((Event)unmarshall(payload));
}
SLF4JLoggerProxy.debug(this,
"GetSnapshotResponse: {}", //$NON-NLS-1$
events);
return events;
} catch (ServiceException | JAXBException e) {
throw new ConnectionException(e);
}
}
/* (non-Javadoc)
* @see org.marketcetera.marketdata.core.webservice.MarketDataServiceClient#getSnapshotPage(org.marketcetera.trade.Instrument, org.marketcetera.marketdata.Content, java.lang.String, org.marketcetera.marketdata.core.webservice.PageRequest)
*/
@Override
public Deque<Event> getSnapshotPage(Instrument inInstrument,
Content inContent,
String inProvider,
PageRequest inPage)
{
SLF4JLoggerProxy.debug(this,
"GetSnapshotPage: {}/{}/{}/{}", //$NON-NLS-1$
inInstrument,
inContent,
inProvider,
inPage);
try(CloseableLock requestLock = CloseableLock.create(serviceLock.readLock())) {
requestLock.lock();
RpcMarketdata.SnapshotPageRequest.Builder requestBuilder = RpcMarketdata.SnapshotPageRequest.newBuilder().setSessionId(sessionId.getValue());
requestBuilder.setContent(RpcMarketdata.ContentAndCapability.valueOf(inContent.name()))
.setInstrument(RpcMarketdata.Instrument.newBuilder().setPayload(marshall(inInstrument)))
.setPage(RpcMarketdata.PageRequest.newBuilder().setPage(inPage.getPage()).setSize(inPage.getSize()));
if(inProvider != null){
requestBuilder.setProvider(inProvider);
}
RpcMarketdata.SnapshotPageResponse response = clientService.getSnapshotPage(controller,
requestBuilder.build());
Deque<Event> events = Lists.newLinkedList();
for(String payload : response.getPayloadList()) {
events.add((Event)unmarshall(payload));
}
SLF4JLoggerProxy.debug(this,
"GetSnapshotPageResponse: {}", //$NON-NLS-1$
events);
return events;
} catch (ServiceException | JAXBException e) {
throw new ConnectionException(e);
}
}
/* (non-Javadoc)
* @see org.marketcetera.marketdata.core.webservice.MarketDataServiceClient#getAvailableCapability()
*/
@Override
public Set<Capability> getAvailableCapability()
{
SLF4JLoggerProxy.debug(this,
"GetAvailableCapability"); //$NON-NLS-1$
try(CloseableLock requestLock = CloseableLock.create(serviceLock.readLock())) {
requestLock.lock();
RpcMarketdata.AvailableCapabilityResponse response = clientService.getAvailableCapability(controller,
RpcMarketdata.AvailableCapabilityRequest.newBuilder().setSessionId(sessionId.getValue()).build());
Set<Capability> capabilities = Sets.newHashSet();
for(RpcMarketdata.ContentAndCapability capability : response.getCapabilityList()) {
capabilities.add(Capability.valueOf(capability.name()));
}
SLF4JLoggerProxy.debug(this,
"GetAvailableCapability: {}", //$NON-NLS-1$
capabilities);
return capabilities;
} catch (ServiceException e) {
throw new ConnectionException(e);
}
}
/* (non-Javadoc)
* @see org.marketcetera.marketdata.core.webservice.MarketDataServiceClient#addServerStatusListener(org.marketcetera.core.notifications.ServerStatusListener)
*/
@Override
public void addServerStatusListener(final ServerStatusListener inListener)
{
synchronized(serverStatusSubscribers) {
ISubscriber subscriberProxy = new ISubscriber() {
@Override
public boolean isInteresting(Object inData)
{
return inData instanceof Boolean;
}
@Override
public void publishTo(Object inData)
{
inListener.receiveServerStatus((Boolean)inData);
}
};
serverStatusSubscribers.put(inListener,
subscriberProxy);
}
inListener.receiveServerStatus(isRunning());
}
/* (non-Javadoc)
* @see org.marketcetera.marketdata.core.webservice.MarketDataServiceClient#removeServerStatusListener(org.marketcetera.core.notifications.ServerStatusListener)
*/
@Override
public void removeServerStatusListener(ServerStatusListener inListener)
{
synchronized(serverStatusSubscribers) {
ISubscriber subscriberProxy = serverStatusSubscribers.remove(inListener);
if(subscriberProxy != null) {
publisher.unsubscribe(subscriberProxy);
}
}
}
/**
* Get the contextClassProvider value.
*
* @return a <code>ContextClassProvider</code> value
*/
public ContextClassProvider getContextClassProvider()
{
return contextClassProvider;
}
/**
* Sets the contextClassProvider value.
*
* @param inContextClassProvider a <code>ContextClassProvider</code> value
*/
public void setContextClassProvider(ContextClassProvider inContextClassProvider)
{
contextClassProvider = inContextClassProvider;
}
/**
* Marshals the given object to an XML stream.
*
* @param inObject an <code>Object</code> value
* @return a <code>String</code> value
* @throws JAXBException if an error occurs marshalling the data
*/
private String marshall(Object inObject)
throws JAXBException
{
StringWriter output = new StringWriter();
synchronized(contextLock) {
marshaller.marshal(inObject,
output);
}
return output.toString();
}
/**
* Unmarshals an object from the given XML stream.
*
* @param inData a <code>String</code> value
* @return a <code>Clazz</code> value
* @throws JAXBException if an error occurs unmarshalling the data
*/
@SuppressWarnings("unchecked")
private <Clazz> Clazz unmarshall(String inData)
throws JAXBException
{
synchronized(contextLock) {
return (Clazz)unmarshaller.unmarshal(new StringReader(inData));
}
}
/**
* Sets the server status to the given value.
*
* <p>This method also notifies subscribers if there is a change in status.
*/
private void setServerStatus(boolean inStatus)
{
if(inStatus == isRunning()) {
return;
}
running.set(inStatus);
synchronized(serverStatusSubscribers) {
for(ISubscriber subscriber : serverStatusSubscribers.values()) {
if(subscriber.isInteresting(inStatus)) {
subscriber.publishTo(inStatus);
}
}
}
}
/**
* Stops the remote service.
*/
private void stopService()
{
try(CloseableLock stopLock = CloseableLock.create(serviceLock.writeLock())) {
stopLock.lock();
try {
clientService.logout(controller,
LogoutRequest.newBuilder().setSessionId(sessionId.getValue()).build());
} catch (Exception ignored) {}
if(executor != null) {
try {
executor.shutdownNow();
} catch (Exception ignored) {}
}
if(channel != null) {
try {
channel.close();
} catch (Exception ignored) {}
}
} finally {
executor = null;
controller = null;
clientService = null;
channel = null;
sessionId = null;
running.set(false);
}
}
/**
* Starts the remote service.
*
* @throws IOException if an error occurs starting the service
* @throws ServiceException if an error occurs starting the service
*/
private void startService()
throws IOException, ServiceException
{
try(CloseableLock startLock = CloseableLock.create(serviceLock.writeLock())) {
startLock.lock();
SLF4JLoggerProxy.debug(this,
"Connecting to RPC server at {}:{}", //$NON-NLS-1$
hostname,
port);
PeerInfo server = new PeerInfo(hostname,
port);
DuplexTcpClientPipelineFactory clientFactory = new DuplexTcpClientPipelineFactory();
executor = new ThreadPoolCallExecutor(1,
10);
clientFactory.setRpcServerCallExecutor(executor);
clientFactory.setConnectResponseTimeoutMillis(10000);
clientFactory.setCompression(true);
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(new NioEventLoopGroup());
bootstrap.handler(clientFactory);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.TCP_NODELAY,
true);
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
10000);
bootstrap.option(ChannelOption.SO_SNDBUF,
1048576);
bootstrap.option(ChannelOption.SO_RCVBUF,
1048576);
channel = clientFactory.peerWith(server,
bootstrap);
clientService = RpcMarketDataService.newBlockingStub(channel);
controller = channel.newRpcController();
java.util.Locale currentLocale = java.util.Locale.getDefault();
LoginRequest loginRequest = LoginRequest.newBuilder()
.setAppId(APP_ID.getValue())
.setVersionId(APP_ID_VERSION.getVersionInfo())
.setClientId(NodeId.generate().getValue())
.setLocale(Locale.newBuilder()
.setCountry(currentLocale.getCountry()==null?"":currentLocale.getCountry()) //$NON-NLS-1$
.setLanguage(currentLocale.getLanguage()==null?"":currentLocale.getLanguage()) //$NON-NLS-1$
.setVariant(currentLocale.getVariant()==null?"":currentLocale.getVariant()).build()) //$NON-NLS-1$
.setUsername(username)
.setPassword(new String(password)).build();
LoginResponse loginResponse = clientService.login(controller,
loginRequest);
sessionId = new SessionId(loginResponse.getSessionId());
setServerStatus(true);
}
}
/**
* Changes the response and throws an exception if there was a problem.
*
* @param inFailed a <code>boolean</code> value
* @param inMessage a <code>String</code> value
*/
private void validateResponse(boolean inFailed,
String inMessage)
{
if(inFailed) {
if(inMessage.contains(NoMarketDataProvidersAvailable.class.getSimpleName())) {
throw new NoMarketDataProvidersAvailable();
}
throw new MarketDataRequestFailed(new I18NBoundMessage1P(Messages.MARKETDATA_ERROR_MESSAGE,
inMessage));
}
}
/**
* Sends heartbeats and monitors the responses.
*
* <p>This class also manages reconnection, if necessary.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: MarketDataRpcClient.java 16901 2014-05-11 16:14:11Z colin $
* @since 2.4.0
*/
@ClassVersion("$Id: MarketDataRpcClient.java 16901 2014-05-11 16:14:11Z colin $")
private class HeartbeatMonitor
implements Runnable
{
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
@Override
public void run()
{
if(!isRunning() && !shutdownRequested.get()) {
try {
stopService();
startService();
} catch (Exception ignored) {}
}
try(CloseableLock heartbeatLock = CloseableLock.create(serviceLock.readLock())) {
heartbeatLock.lock();
clientService.heartbeat(controller,
RpcMarketdata.HeartbeatRequest.newBuilder().setId(System.nanoTime()).build());
} catch (Exception e) {
// heartbeat failed for some reason
SLF4JLoggerProxy.debug(MarketDataRpcClient.this,
e,
"Heartbeat failed"); //$NON-NLS-1$
setServerStatus(false);
}
}
}
/**
* indicates that a shutdown has been requested
*/
private final AtomicBoolean shutdownRequested = new AtomicBoolean(false);
/**
* stores a handle to the heartbeat scheduled job
*/
private ScheduledFuture<?> heartbeatFuture;
/**
* executes heartbeats
*/
private final ScheduledExecutorService heartbeatService = Executors.newScheduledThreadPool(1);
/**
* tracks subscribers to connection status changes
*/
@GuardedBy("serverStatusSubscribers")
private final Map<ServerStatusListener,ISubscriber> serverStatusSubscribers = Maps.newHashMap();
/**
* publishes notifications of connection status changes
*/
private final PublisherEngine publisher = new PublisherEngine(true);
/**
* indicates if the connection is up and running or not
*/
private final AtomicBoolean running = new AtomicBoolean(false);
/**
* guards access to RPC service objects
*/
private final ReadWriteLock serviceLock = new ReentrantReadWriteLock();
/**
* provides access to RPC services
*/
private BlockingInterface clientService;
/**
* executes the nitty-gritty of the calls
*/
private RpcServerCallExecutor executor;
/**
* channel over which calls are made
*/
private RpcClientChannel channel;
/**
* controller responsible for the RPC connection
*/
private RpcController controller;
/**
* username with which to connect
*/
private String username;
/**
* password with which to connect
*/
private String password;
/**
* hostname to which to connect
*/
private String hostname;
/**
* port to which to connect
*/
private int port;
/**
* provides context classes for marshalling/unmarshalling, may be <code>null</code>
*/
private ContextClassProvider contextClassProvider;
/**
* session ID value for this connection, may be <code>null</code> if the connection is inactive
*/
private SessionId sessionId;
/**
* guards access to JAXB context objects
*/
private final Object contextLock = new Object();
/**
* context used to serialize and unserialize messages as necessary
*/
@GuardedBy("contextLock")
private JAXBContext context;
/**
* marshals messages
*/
@GuardedBy("contextLock")
private Marshaller marshaller;
/**
* unmarshals messages
*/
@GuardedBy("contextLock")
private Unmarshaller unmarshaller;
/**
* interval at which to execute heartbeats
*/
private long heartbeatInterval = 10000;
/**
* The client's application ID: the application name.
*/
public static final String APP_ID_NAME = "RpcClient"; //$NON-NLS-1$
/**
* The client's application ID: the version.
*/
public static final VersionInfo APP_ID_VERSION = ApplicationVersion.getVersion(MarketDataRpcClient.class);
/**
* The client's application ID: the ID.
*/
public static final AppId APP_ID = Util.getAppId(APP_ID_NAME,APP_ID_VERSION.getVersionInfo());
}