package org.marketcetera.strategy;
import static org.marketcetera.strategy.Messages.*;
import java.io.File;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.concurrent.GuardedBy;
import javax.management.*;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.marketcetera.client.*;
import org.marketcetera.client.brokers.BrokerStatus;
import org.marketcetera.core.ApplicationContainer;
import org.marketcetera.core.CloseableLock;
import org.marketcetera.core.Util;
import org.marketcetera.core.notifications.Notification;
import org.marketcetera.core.position.PositionKey;
import org.marketcetera.core.publisher.ISubscriber;
import org.marketcetera.core.publisher.PublisherEngine;
import org.marketcetera.event.Event;
import org.marketcetera.event.LogEvent;
import org.marketcetera.event.LogEventLevel;
import org.marketcetera.event.impl.LogEventBuilder;
import org.marketcetera.marketdata.MarketDataRequest;
import org.marketcetera.marketdata.core.manager.MarketDataManager;
import org.marketcetera.metrics.ThreadedMetric;
import org.marketcetera.module.*;
import org.marketcetera.trade.*;
import org.marketcetera.trade.Currency;
import org.marketcetera.util.log.I18NBoundMessage1P;
import org.marketcetera.util.log.I18NBoundMessage2P;
import org.marketcetera.util.log.I18NBoundMessage3P;
import org.marketcetera.util.log.SLF4JLoggerProxy;
import org.marketcetera.util.misc.ClassVersion;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import quickfix.Message;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/* $License$ */
/**
* Strategy Agent implementation for the <code>Strategy</code> module.
* <p>
* Module Features
* <table>
* <tr><th>Capabilities</th><td>Data Emitter, Data Receiver, Data Flow Requester</td></tr>
* <tr><th>Stops data flows</th><td>No</td></tr>
* <tr><th>Start Operation</th><td>Plumbs all the data flows, compiles the strategy.</td></tr>
* <tr><th>Stop Operation</th><td>Stops the strategy, unplumbs all the dataflows</td></tr>
* <tr><th>Management Interface</th><td>{@link StrategyMXBean}</td></tr>
* <tr><th>MX Notification</th><td>{@link AttributeChangeNotification}
* whenever {@link #getStatus()} changes. </td></tr>
* <tr><th>Factory</th><td>{@link StrategyModuleFactory}</td></tr>
* </table>
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: StrategyModule.java 16899 2014-05-11 16:03:04Z colin $
* @since 1.0.0
*/
@ClassVersion("$Id: StrategyModule.java 16899 2014-05-11 16:03:04Z colin $")
final class StrategyModule
extends Module
implements DataEmitter, DataFlowRequester, DataReceiver, ServicesProvider, StrategyMXBean, NotificationEmitter
{
/* (non-Javadoc)
* @see org.marketcetera.module.DataEmitter#cancel(org.marketcetera.module.RequestID)
*/
@Override
public void cancel(DataFlowID inFlowID,
RequestID inRequestID)
{
synchronized(subscribers) {
DataRequester requester = subscribers.remove(inRequestID);
if(requester != null) {
requester.unsubscribe();
}
}
}
/* (non-Javadoc)
* @see org.marketcetera.module.DataEmitter#requestData(org.marketcetera.module.DataRequest, org.marketcetera.module.DataEmitterSupport)
*/
@Override
public void requestData(DataRequest inRequest,
DataEmitterSupport inSupport)
throws UnsupportedRequestParameterType, IllegalRequestParameterValue
{
Object requestPayload = inRequest.getData();
OutputType request;
if(requestPayload == null) {
throw new IllegalRequestParameterValue(getURN(),
null);
}
if(requestPayload instanceof String) {
try {
request = OutputType.valueOf(((String)requestPayload).toUpperCase());
} catch (Exception e) {
throw new IllegalRequestParameterValue(getURN(),
requestPayload);
}
} else if(requestPayload instanceof OutputType) {
request = (OutputType)requestPayload;
} else if(requestPayload instanceof InternalRequest) {
InternalRequest internalRequest = (InternalRequest)requestPayload;
SLF4JLoggerProxy.debug(StrategyModule.class,
"{} received a request to set up a specialized data flow to {}", //$NON-NLS-1$
strategy,
internalRequest.originalRequester); //$NON-NLS-1$
internalDataFlows.put(internalRequest.originalRequester,
inSupport);
return;
} else {
throw new UnsupportedRequestParameterType(getURN(),
requestPayload);
}
subscribe(request,
inSupport);
}
/* (non-Javadoc)
* @see org.marketcetera.module.DataFlowRequester#setFlowSupport(org.marketcetera.module.DataFlowSupport)
*/
@Override
public void setFlowSupport(DataFlowSupport inSupport)
{
dataFlowSupport = inSupport;
}
/* (non-Javadoc)
* @see org.marketcetera.module.DataReceiver#receiveData(org.marketcetera.module.DataFlowID, java.lang.Object)
*/
@Override
public void receiveData(DataFlowID inFlowID,
Object inData)
throws UnsupportedDataTypeException, StopDataFlowException
{
ThreadedMetric.event("strategy-IN"); //$NON-NLS-1$
assertStateForReceiveData();
SLF4JLoggerProxy.trace(StrategyModule.class,
"{} received {}", //$NON-NLS-1$
strategy,
inData);
if(inData instanceof Event) {
Event event = (Event)inData;
setEventSource(event,
inFlowID);
}
strategy.dataReceived(inData);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.OutboundServicesProvider#cancelOrder(org.marketcetera.trade.OrderCancel)
*/
@Override
public void cancelOrder(OrderCancel inCancel)
{
publish(inCancel);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.OutboundServicesProvider#cancelReplace(org.marketcetera.trade.OrderReplace)
*/
@Override
public void cancelReplace(OrderReplace inReplace)
{
publish(inReplace);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.OutboundServices#marketDataRequest(org.marketcetera.marketdata.MarketDataRequest, java.lang.String)
*/
@Override
public int requestMarketData(MarketDataRequest inRequest)
{
if(inRequest == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_MARKET_DATA_REQUEST,
String.valueOf(strategy),
inRequest).create(),
strategy);
return 0;
}
return doMarketDataRequest(inRequest);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.OutboundServicesProvider#requestMarketDataWithCEP(org.marketcetera.marketdata.MarketDataRequest, java.lang.String, java.lang.String[], java.lang.String, java.lang.String)
*/
@Override
public int requestProcessedMarketData(MarketDataRequest inRequest,
String[] inStatements,
String inCEPSource,
String inNamespace)
{
if(inRequest == null ||
inStatements == null ||
inStatements.length == 0 ||
inCEPSource == null ||
inCEPSource.isEmpty() ||
inNamespace == null ||
inNamespace.isEmpty()) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_COMBINED_DATA_REQUEST,
String.valueOf(strategy),
inRequest,
Arrays.toString(inStatements),
inCEPSource,
inNamespace).create(),
strategy);
return 0;
}
int requestID = counter.incrementAndGet();
// construct a request that connects the provider to the cep query
try {
ModuleURN marketDataURN = constructMarketDataUrn(inRequest.getProvider());
ModuleURN cepDataURN = constructCepUrn(inCEPSource,
inNamespace);
SLF4JLoggerProxy.debug(StrategyModule.class,
"{} received a processed market data request {} for market data from {} via {}", //$NON-NLS-1$
strategy,
inRequest,
marketDataURN,
cepDataURN);
try(CloseableLock closeableLock = CloseableLock.create(dataFlowLock.writeLock())) {
closeableLock.lock();
DataFlowID dataFlowID = dataFlowSupport.createDataFlow(new DataRequest[] { new DataRequest(marketDataURN,
inRequest),
new DataRequest(cepDataURN,
determineCepStatements(inCEPSource,
inStatements)),
new DataRequest(getURN()) },
false);
new RequestContainer(dataFlowID,
requestID);
}
return requestID;
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(COMBINED_DATA_REQUEST_FAILED,
inRequest,
Arrays.toString(inStatements),
inCEPSource,
inNamespace)
.withException(e).create(),
strategy);
return 0;
}
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.OutboundServices#cancelAllDataRequests()
*/
@Override
public void cancelAllDataRequests()
{
try(CloseableLock closeableLock = CloseableLock.create(dataFlowLock.writeLock())) {
closeableLock.lock();
Set<RequestContainer> requests = Sets.newHashSet();
requests.addAll(requestsByDataFlowId.values());
requests.addAll(requestsByInternalId.values());
for(RequestContainer container : requests) {
try {
container.cancel();
} catch (Exception e) {
SLF4JLoggerProxy.warn(this,
e,
Messages.UNABLE_TO_CANCEL_DATA_REQUEST.getText(strategy,
container));
StrategyModule.log(LogEventBuilder.warn().withMessage(UNABLE_TO_CANCEL_DATA_REQUEST,
String.valueOf(strategy),
String.valueOf(container))
.withException(e).create(),
strategy);
}
}
}
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.OutboundServices#cancelDataRequest(int)
*/
@Override
public void cancelDataRequest(int inDataRequestID)
{
try(CloseableLock closeableLock = CloseableLock.create(dataFlowLock.writeLock())) {
closeableLock.lock();
RequestContainer container = getDataRequestBy(inDataRequestID);
if(container == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(NO_DATA_HANDLE,
String.valueOf(strategy),
inDataRequestID).create(),
strategy);
return;
}
container.cancel();
}
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.OutboundServicesProvider#requestCEPData(java.lang.String[], java.lang.String)
*/
@Override
public int requestCEPData(String[] inStatements,
String inSource,
String inNamespace)
{
if(inStatements == null ||
inStatements.length == 0) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_CEP_REQUEST,
String.valueOf(strategy),
Arrays.toString(inStatements),
String.valueOf(inSource),
String.valueOf(inNamespace)).create(),
strategy);
return 0;
}
int requestID = counter.incrementAndGet();
ModuleURN providerURN = constructCepUrn(inSource,
inNamespace);
try(CloseableLock closeableLock = CloseableLock.create(dataFlowLock.writeLock())) {
closeableLock.lock();
try {
DataFlowID flowID = dataFlowSupport.createDataFlow(new DataRequest[] { new DataRequest(providerURN,
determineCepStatements(inSource,
inStatements)),
new DataRequest(getURN()) },
false);
new RequestContainer(flowID,
requestID);
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CEP_REQUEST_FAILED,
Arrays.toString(inStatements),
inSource)
.withException(e).create(),
strategy);
return 0;
}
return requestID;
}
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.StrategyMXBean#setOrdersDestination(java.lang.String)
*/
@Override
public void setOutputDestination(String inDestination)
{
if(inDestination == null ||
inDestination.isEmpty()) {
outputDestination = null;
SLF4JLoggerProxy.debug(StrategyModule.class,
"Setting output destination to null"); //$NON-NLS-1$
return;
}
outputDestination = new ModuleURN(inDestination);
SLF4JLoggerProxy.debug(StrategyModule.class,
"Setting output destination to {}", //$NON-NLS-1$
outputDestination);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.StrategyMXBean#setParameters(java.lang.String)
*/
@Override
public void setParameters(String inParameters)
{
if(inParameters == null ||
inParameters.isEmpty()) {
parameters = null;
SLF4JLoggerProxy.debug(StrategyModule.class,
"Setting parameters to null"); //$NON-NLS-1$
return;
}
if(parameters != null) {
parameters.clear();
}
parameters = Util.propertiesFromString(inParameters);
SLF4JLoggerProxy.debug(StrategyModule.class,
"Setting parameters to {}", //$NON-NLS-1$
parameters);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.StrategyMXBean#getOrdersDestination()
*/
@Override
public String getOutputDestination()
{
if(outputDestination == null) {
return null;
}
return outputDestination.getValue();
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.StrategyMXBean#isRoutingOrdersToORS()
*/
@Override
public boolean isRoutingOrdersToORS()
{
synchronized(this) {
return routeOrdersToORS;
}
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.StrategyMXBean#setRoutingOrdersToORS(boolean)
*/
@Override
public void setRoutingOrdersToORS(boolean inValue)
{
try {
if(routeOrdersToORS != inValue) {
if(getState().isStarted()) {
if(inValue) {
establishORSRouting();
} else {
disconnectORSRouting();
}
}
}
// change the value only if the above call succeeded
routeOrdersToORS = inValue;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.StrategyMXBean#getName()
*/
@Override
public String getName()
{
return name;
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.StrategyMXBean#getLanguage()
*/
@Override
public Language getLanguage()
{
return type;
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.StrategyMXBean#getParameters()
*/
@Override
public String getParameters()
{
if(parameters == null) {
return null;
}
return Util.propertiesToString(parameters);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.OutboundServicesProvider#setMessage(quickfix.Message)
*/
@Override
public void sendMessage(Message inMessage,
BrokerID inBroker)
{
if(inMessage == null ||
inBroker == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_MESSAGE,
String.valueOf(strategy)).create(),
strategy);
return;
}
try {
publish(Factory.getInstance().createOrder(inMessage,
inBroker));
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(SEND_MESSAGE_FAILED,
String.valueOf(strategy),
inMessage,
inBroker)
.withException(e).create(),
strategy);
}
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.OutboundServicesProvider#sendOther(java.lang.Object)
*/
@Override
public void send(Object inData)
{
if(inData == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_DATA,
String.valueOf(strategy)).create(),
strategy);
return;
}
publish(inData);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.OutboundServices#sendSuggestion(java.lang.Object)
*/
@Override
public void sendSuggestion(Suggestion inSuggestion)
{
if(inSuggestion == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_TRADE_SUGGESTION,
String.valueOf(strategy)).create(),
strategy);
return;
}
publish(inSuggestion);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.OutboundServicesProvider#sendEvent(org.marketcetera.event.EventBase)
*/
@Override
public void sendEvent(Event inEvent,
String inProvider,
String inNamespace)
{
// event must not be null, but the other two parameters may be
if(inEvent == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_EVENT,
String.valueOf(strategy)).create(),
strategy);
return;
}
// event is not null, check that provider and namespace are not null
if(inProvider != null &&
!inProvider.isEmpty() &&
inNamespace != null &&
!inNamespace.isEmpty()) {
// event, provider, and namespace are not null, send the event to the described cep module
ModuleURN cepModuleURN = constructCepUrn(inProvider,
inNamespace);
try {
sendEventToCEP(cepModuleURN,
inEvent);
} catch (Exception e) {
// warn that the event was not sent to CEP, but continue to send to subscribers
// this may not be an error as the CEP module may not exist
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_SEND_EVENT_TO_CEP,
String.valueOf(strategy),
inEvent,
cepModuleURN).create(),
strategy);
}
// done sending event to CEP, for better or worse
}
// send to subscribers
publish(inEvent);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.OutboundServicesProvider#setNotification(org.marketcetera.core.notifications.Notification)
*/
@Override
public void sendNotification(Notification inNotification)
{
if(inNotification == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_NOTIFICATION,
String.valueOf(strategy)).create(),
strategy);
return;
}
publish(inNotification);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.OutboundServicesProvider#log(java.lang.String)
*/
@Override
public void log(LogEvent inEvent)
{
if(inEvent == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(INVALID_LOG,
String.valueOf(strategy)).create(),
strategy);
return;
}
if(shouldLog(inEvent)) {
publish(inEvent);
}
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.InboundServicesProvider#getBrokers()
*/
@Override
public List<BrokerStatus> getBrokers()
throws ConnectionException, ClientInitException
{
return clientFactory.getClient().getBrokersStatus().getBrokers();
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.InboundServicesProvider#getEquityPositionAsOf(java.util.Date, org.marketcetera.trade.Equity)
*/
@Override
public BigDecimal getPositionAsOf(Date inDate,
Equity inEquity)
throws ConnectionException, ClientInitException
{
return clientFactory.getClient().getEquityPositionAsOf(inDate,
inEquity);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.ServicesProvider#getAllOptionPositionsAsOf(java.util.Date)
*/
@Override
public Map<PositionKey<Option>, BigDecimal> getAllOptionPositionsAsOf(Date inDate)
throws ConnectionException, ClientInitException
{
return clientFactory.getClient().getAllOptionPositionsAsOf(inDate);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.ServicesProvider#getAllFuturePositionsAsOf(java.util.Date)
*/
@Override
public Map<PositionKey<Future>, BigDecimal> getAllFuturePositionsAsOf(Date inDate)
throws ConnectionException, ClientInitException
{
return clientFactory.getClient().getAllFuturePositionsAsOf(inDate);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.ServicesProvider#getFuturePositionAsOf(java.util.Date, org.marketcetera.trade.Future)
*/
@Override
public BigDecimal getFuturePositionAsOf(Date inDate,
Future inFuture)
throws ConnectionException, ClientInitException
{
return clientFactory.getClient().getFuturePositionAsOf(inDate,
inFuture);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.ServicesProvider#getAllCurrencyPositionsAsOf(java.util.Date)
*/
@Override
public Map<PositionKey<Currency>, BigDecimal> getAllCurrencyPositionsAsOf(Date inDate)
throws ConnectionException, ClientInitException
{
return clientFactory.getClient().getAllCurrencyPositionsAsOf(inDate);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.ServicesProvider#getCurrencyPositionAsOf(java.util.Date, org.marketcetera.trade.Currency)
*/
@Override
public BigDecimal getCurrencyPositionAsOf(Date inDate,
Currency inCurrency)
throws ConnectionException, ClientInitException
{
return clientFactory.getClient().getCurrencyPositionAsOf(inDate,
inCurrency);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.ServicesProvider#getOptionPositionAsOf(java.util.Date, org.marketcetera.trade.Option)
*/
@Override
public BigDecimal getOptionPositionAsOf(Date inDate,
Option inOption)
throws ConnectionException, ClientInitException
{
return clientFactory.getClient().getOptionPositionAsOf(inDate,
inOption);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.ServicesProvider#getOptionPositionsAsOf(java.util.Date, java.lang.String[])
*/
@Override
public Map<PositionKey<Option>, BigDecimal> getOptionPositionsAsOf(Date inDate,
String... inOptionRoots)
throws ConnectionException, ClientInitException
{
return clientFactory.getClient().getOptionPositionsAsOf(inDate,
inOptionRoots);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.ServicesProvider#getOptionRoots(java.lang.String)
*/
@Override
public Collection<String> getOptionRoots(String inUnderlying)
throws ConnectionException, ClientInitException
{
return clientFactory.getClient().getOptionRoots(inUnderlying);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.ServicesProvider#getAllPositionsAsOf(java.util.Date)
*/
@Override
public Map<PositionKey<Equity>, BigDecimal> getAllPositionsAsOf(Date inDate)
throws ConnectionException, ClientInitException
{
return clientFactory.getClient().getAllEquityPositionsAsOf(inDate);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.ServicesProvider#getUnderlying(java.lang.String)
*/
@Override
public String getUnderlying(String inOptionRoot)
throws ConnectionException, ClientInitException
{
return clientFactory.getClient().getUnderlying(inOptionRoot);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.StrategyMXBean#getStatus()
*/
@Override
public String getStatus()
{
return strategy.getStatus().toString();
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.OutboundServicesProvider#statusChanged(org.marketcetera.strategy.Status, org.marketcetera.strategy.Status)
*/
@Override
public void statusChanged(Status inOldStatus,
Status inNewStatus)
{
notificationDelegate.sendNotification(new AttributeChangeNotification(this,
jmxNotificationCounter.getAndIncrement(),
System.currentTimeMillis(),
STATUS_CHANGED.getText(),
"Status", //$NON-NLS-1$
"String", //$NON-NLS-1$
inOldStatus,
inNewStatus));
}
/* (non-Javadoc)
* @see javax.management.NotificationEmitter#removeNotificationListener(javax.management.NotificationListener, javax.management.NotificationFilter, java.lang.Object)
*/
@Override
public void removeNotificationListener(NotificationListener inListener,
NotificationFilter inFilter,
Object inHandback)
throws ListenerNotFoundException
{
notificationDelegate.removeNotificationListener(inListener,
inFilter,
inHandback);
}
/* (non-Javadoc)
* @see javax.management.NotificationBroadcaster#addNotificationListener(javax.management.NotificationListener, javax.management.NotificationFilter, java.lang.Object)
*/
@Override
public void addNotificationListener(NotificationListener inListener,
NotificationFilter inFilter,
Object inHandback)
throws IllegalArgumentException
{
notificationDelegate.addNotificationListener(inListener,
inFilter,
inHandback);
}
/* (non-Javadoc)
* @see javax.management.NotificationBroadcaster#getNotificationInfo()
*/
@Override
public MBeanNotificationInfo[] getNotificationInfo()
{
return notificationDelegate.getNotificationInfo();
}
/* (non-Javadoc)
* @see javax.management.NotificationBroadcaster#removeNotificationListener(javax.management.NotificationListener)
*/
@Override
public void removeNotificationListener(NotificationListener inListener)
throws ListenerNotFoundException
{
notificationDelegate.removeNotificationListener(inListener);
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.OutboundServicesProvider#cancelDataFlow(org.marketcetera.module.DataFlowID)
*/
@Override
public void cancelDataFlow(DataFlowID inDataFlowID)
throws ModuleException
{
try(CloseableLock closeableLock = CloseableLock.create(dataFlowLock.writeLock())) {
closeableLock.lock();
RequestContainer container = getDataRequestBy(inDataFlowID);
if(container != null) {
container.cancel();
} else {
dataFlowSupport.cancel(inDataFlowID);
}
}
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.OutboundServicesProvider#createDataFlow(org.marketcetera.module.DataRequest[], boolean)
*/
@Override
public DataFlowID createDataFlow(DataRequest[] inRequests,
boolean inAppendDataSink)
throws ModuleException
{
try(CloseableLock closeableLock = CloseableLock.create(dataFlowLock.writeLock())) {
closeableLock.lock();
DataFlowID dataFlowId = dataFlowSupport.createDataFlow(inRequests,
inAppendDataSink);
new RequestContainer(dataFlowId,
counter.incrementAndGet());
return dataFlowId;
}
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.ServicesProvider#getUserData()
*/
@Override
public Properties getUserData()
throws ConnectionException, ClientInitException
{
Properties data = clientFactory.getClient().getUserData();
return data == null ? new Properties() : data;
}
/* (non-Javadoc)
* @see org.marketcetera.strategy.ServicesProvider#setUserData(java.util.Properties)
*/
@Override
public void setUserData(Properties inData)
throws ConnectionException, ClientInitException
{
clientFactory.getClient().setUserData(inData);
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
StringBuilder output = new StringBuilder();
output.append(type).append(" strategy ").append(name); //$NON-NLS-1$
return output.toString();
}
/**
* Logs the given event.
*
* @param inEvent a <code>LogEvent</code> value
* @param inStrategy a <code>Strategy</code> value
*/
static void log(LogEvent inEvent,
Strategy inStrategy)
{
if(shouldLog(inEvent)) {
doLogger(inEvent);
inStrategy.getServicesProvider().log(inEvent);
}
}
/**
* Determines if the given event should be emitted or not.
*
* @param inEvent a <code>LogEvent</code> value
* @return a <code>boolean</code> value
*/
private static boolean shouldLog(LogEvent inEvent) {
return LogEventLevel.shouldLog(inEvent,
Strategy.STRATEGY_MESSAGES);
}
/**
* Logs the given message to the standard log.
*
* @param inEvent a <code>LogEvent</code> value
*/
private static void doLogger(LogEvent inEvent)
{
Throwable exception = inEvent.getException();
String message = inEvent.getMessage();
if(LogEventLevel.DEBUG.equals(inEvent.getLevel())) {
if(exception == null) {
MESSAGE_1P.debug(Strategy.STRATEGY_MESSAGES,
message);
} else {
MESSAGE_1P.debug(Strategy.STRATEGY_MESSAGES,
exception,
message);
}
} else if(LogEventLevel.INFO.equals(inEvent.getLevel())) {
if(exception == null) {
MESSAGE_1P.info(Strategy.STRATEGY_MESSAGES,
message);
} else {
MESSAGE_1P.info(Strategy.STRATEGY_MESSAGES,
exception,
message);
}
} else if(LogEventLevel.WARN.equals(inEvent.getLevel())) {
if(exception == null) {
MESSAGE_1P.warn(Strategy.STRATEGY_MESSAGES,
message);
} else {
MESSAGE_1P.warn(Strategy.STRATEGY_MESSAGES,
exception,
message);
}
} else if(LogEventLevel.ERROR.equals(inEvent.getLevel())) {
if(exception == null) {
MESSAGE_1P.error(Strategy.STRATEGY_MESSAGES,
message);
} else {
MESSAGE_1P.error(Strategy.STRATEGY_MESSAGES,
exception,
message);
}
}
}
/**
* Gets a <code>StrategyModule</code> instance with the given parameters.
*
* <p>The module returned is guaranteed to be a valid, unstarted <code>StrategyModule</code>.
*
* @param inParameters an <code>Object...</code> value
* @return a <code>StrategyModule</code> value
* @throws ModuleCreationException if the module cannot be created
*/
static StrategyModule getStrategyModule(Object...inParameters)
throws ModuleCreationException
{
// must have 7 parameters, though the first and the last four may be null
if(inParameters == null ||
inParameters.length != 7) {
throw new ModuleCreationException(PARAMETER_COUNT_ERROR);
}
// parameter 1 is the URN name of the strategy or null for the system to create a name
String instanceName = null;
if(inParameters[0] != null) {
if(inParameters[0] instanceof String) {
if(((String)inParameters[0]).isEmpty()) {
throw new ModuleCreationException(EMPTY_INSTANCE_ERROR);
} else {
instanceName = (String)inParameters[0];
}
} else {
throw new ModuleCreationException(new I18NBoundMessage3P(PARAMETER_TYPE_ERROR,
1,
String.class.getName(),
inParameters[0].getClass().getName()));
}
}
// parameter 2 is the strategy name (human-readable) and must be non-null and have non-zero length
String name;
if(inParameters[1] == null) {
throw new ModuleCreationException(new I18NBoundMessage2P(NULL_PARAMETER_ERROR,
2,
String.class.getName()));
}
if(inParameters[1] instanceof String) {
if(((String)inParameters[1]).isEmpty()) {
throw new ModuleCreationException(EMPTY_NAME_ERROR);
} else {
name = (String)inParameters[1];
}
} else {
throw new ModuleCreationException(new I18NBoundMessage3P(PARAMETER_TYPE_ERROR,
2,
String.class.getName(),
inParameters[1].getClass().getName()));
}
// parameter 3 is the language. the parameter may be of type Language or may be a String describing the contents of a Language enum value
Language type = null;
if(inParameters[2] == null) {
throw new ModuleCreationException(new I18NBoundMessage2P(NULL_PARAMETER_ERROR,
3,
Language.class.getName()));
}
if(inParameters[2] instanceof Language) {
type = (Language)inParameters[2];
} else {
if(inParameters[2] instanceof String) {
try {
type = Language.valueOf(((String)inParameters[2]).toUpperCase());
} catch (Exception e) {
throw new ModuleCreationException(new I18NBoundMessage1P(INVALID_LANGUAGE_ERROR,
inParameters[2].toString()));
}
} else {
throw new ModuleCreationException(new I18NBoundMessage3P(PARAMETER_TYPE_ERROR,
3,
Language.class.getName(),
inParameters[2].getClass().getName()));
}
}
// parameter 4 is the strategy source. the parameter may be null. if non-null, must be a File which exists and is readable
File source = null;
if(type == Language.RUBY && inParameters[3] == null) {
throw new ModuleCreationException(new I18NBoundMessage2P(NULL_PARAMETER_ERROR,
4,
File.class.getName()));
}
if(inParameters[3] != null) {
if(inParameters[3] instanceof File) {
source = (File)inParameters[3];
if(StringUtils.trimToNull(source.getName()) == null) {
source = null;
} else {
if(!(source.exists() || source.canRead())) {
throw new ModuleCreationException(new I18NBoundMessage1P(FILE_DOES_NOT_EXIST_OR_IS_NOT_READABLE,
source.getAbsolutePath()));
}
}
} else {
throw new ModuleCreationException(new I18NBoundMessage3P(PARAMETER_TYPE_ERROR,
4,
File.class.getName(),
inParameters[3].getClass().getName()));
}
}
// parameter 5 is a Properties object. The parameter may be null. If non-null, these values are made available to the running strategy.
Properties parameters = null;
if(inParameters[4] != null) {
if(inParameters[4] instanceof Properties) {
parameters = (Properties)inParameters[4];
} else {
throw new ModuleCreationException(new I18NBoundMessage3P(PARAMETER_TYPE_ERROR,
5,
Properties.class.getName(),
inParameters[4].getClass().getName()));
}
}
// parameter 6 is a Boolean. This parameter may be null. If non-null and true, it indicates that this strategy should route outgoing
// orders to the ORS client. Otherwise, orders will be swallowed.
boolean routeOrdersToORS = false;
if(inParameters[5] != null) {
if(inParameters[5] instanceof Boolean) {
routeOrdersToORS = (Boolean)inParameters[5];
} else {
throw new ModuleCreationException(new I18NBoundMessage3P(PARAMETER_TYPE_ERROR,
6,
Boolean.class.getName(),
inParameters[5].getClass().getName()));
}
}
// parameter 7 is a ModuleURN. This parameter may be null. If non-null, it must describe a module instance that is started and able
// to receive data. All output will be sent to this module.
ModuleURN outputInstance = null;
if(inParameters[6] != null) {
if(inParameters[6] instanceof ModuleURN) {
outputInstance = (ModuleURN)inParameters[6];
} else {
throw new ModuleCreationException(new I18NBoundMessage3P(PARAMETER_TYPE_ERROR,
7,
ModuleURN.class.getName(),
inParameters[6].getClass().getName()));
}
}
return new StrategyModule(instanceName == null ? generateInstanceURN(name) : new ModuleURN(StrategyModuleFactory.PROVIDER_URN,
instanceName),
name,
type,
source,
parameters,
routeOrdersToORS,
outputInstance);
}
/* (non-Javadoc)
* @see org.marketcetera.module.Module#preStart()
*/
@Override
protected void preStart()
throws ModuleException
{
assertStateForPreStart();
// add destination data flows, if specified by the object parameters
if(outputDestination != null) {
createDataFlow(new DataRequest[] { new DataRequest(getURN(),
OutputType.ALL),
new DataRequest(outputDestination) },
false);
}
// set the connection to the ORS to the correct value
if(routeOrdersToORS) {
establishORSRouting();
} else {
disconnectORSRouting();
}
// request execution reports from the ORS client
try(CloseableLock closeableLock = CloseableLock.create(dataFlowLock.writeLock())) {
closeableLock.lock();
try {
DataFlowID reportsDataFlow = dataFlowSupport.createDataFlow(new DataRequest[] { new DataRequest(ClientModuleFactory.INSTANCE_URN),
new DataRequest(getURN()) },
false);
new RequestContainer(reportsDataFlow,
counter.incrementAndGet());
} catch (Exception e) {
EXECUTION_REPORT_REQUEST_FAILED.warn(StrategyModule.class,
name,
ClientModuleFactory.INSTANCE_URN);
}
}
@SuppressWarnings("resource")
ApplicationContext context = ApplicationContainer.getInstance()==null?null:ApplicationContainer.getInstance().getContext();
if(context != null) {
try {
marketDataManager = context.getBean(MarketDataManager.class);
} catch (NoSuchBeanDefinitionException e) {
SLF4JLoggerProxy.debug(this,
"No market data manager, falling back on market data module framework"); //$NON-NLS-1$
}
}
try {
strategy = new StrategyImpl(name,
getURN().getValue(),
type,
source,
parameters,
getURN().instanceName(),
this);
strategy.start();
} catch (Exception e) {
throw new ModuleException(e,
FAILED_TO_START);
}
}
/* (non-Javadoc)
* @see org.marketcetera.module.Module#preStop()
*/
@Override
protected void preStop()
throws ModuleException
{
try {
strategy.stop();
} catch (ModuleException e) {
throw e;
} catch (Exception e) {
// otherwise a strategy may not prevent itself from being stopped
STOP_ERROR.warn(StrategyModule.class,
e,
strategy);
}
cancelAllDataRequests();
disconnectORSRouting();
}
/**
* Generates an instance URN guaranteed to be unique with the scope of this JVM.
*
* @return a <code>ModuleURN</code> value
*/
private static final ModuleURN generateInstanceURN(String inName)
{
// the sanitized name is constructed to match the restrictions of the module framework for URN names
// notice that this works for ASCII stuff only, which is what we want. if the incoming name is non-ASCII,
// all the characters will be removed, again, according to how the module framework demands.
String sanitizedName = inName.replaceAll("[^A-Z|a-z|0-9]", //$NON-NLS-1$
""); //$NON-NLS-1$
return new ModuleURN(StrategyModuleFactory.PROVIDER_URN,
String.format("strategy%s%s", //$NON-NLS-1$
sanitizedName,
Integer.toHexString(counter.incrementAndGet())));
}
/**
* Constructs a <code>ModuleURN</code> for a <code>CEP</code> module from the given components.
*
* @param inSource a <code>String</code> value containing the name of a <code>CEP</code> provider
* @param inNamespace a <code>String</code> value containing the namespace of a <code>CEP</code> provider query
* @return a <code>ModuleURN</code> value containing the URN of the CEP module - there is no guarantee that this
* URN exists or is started
*/
private static ModuleURN constructCepUrn(String inSource,
String inNamespace)
{
// this should live somewhere in CEP - don't like this
assert(inSource != null);
assert(inNamespace != null);
return new ModuleURN(String.format("metc:cep:%s:%s", //$NON-NLS-1$
inSource,
inNamespace));
}
/**
* Determines the correct set of statements to return depending on the CEP source.
*
* This method is a hack to get around the lack of a consistent API for CEP. One provider
* takes a different set of parameters than the other.
*
* @param inSource a <code>String</code> value containing the name of the CEP provider
* @param inStatements a <code>String[]</code> value containing the CEP statements
* @return an <code>Object</code> value containing the CEP statements to pass to the provider
*/
private static Object determineCepStatements(String inSource,
String[] inStatements)
{
if(inSource.equals("esper")) { //$NON-NLS-1$
return inStatements;
} else {
return inStatements[0];
}
}
/**
* Constructs a <code>ModuleURN</code> to use to request market data.
*
* @param inSource a <code>String</code> value containing the name of the provider
* @return
*/
private static ModuleURN constructMarketDataUrn(String inSource)
{
return new ModuleURN(String.format("metc:mdata:%s", //$NON-NLS-1$
inSource));
}
/**
* Create a new StrategyModule instance.
*
* @param inURN a <code>ModuleURN</code> value containing the instance URN for this strategy
* @param inName a <code>String</code> value containing the user-specified, human-readable name for this strategy
* @param inType a <code>Language</code> value containing the language type as which to execute this strategy
* @param inSource a <code>File</code> value containing the source of the strategy code
* @param inParameters a <code>Properties</code> value containing parameters to pass to the strategy. may be null.
* @param inRouteOrdersToORS a <code>boolean</code> value indicating whether to route orders to the ORS or not
* @param inOutputInstance a <code>ModuleURN</code> value containing a {@link DataReceiver} to which to pass outputs generated by this strategy. may be null.
* @throws ModuleCreationException if the strategy cannot be created
*/
private StrategyModule(ModuleURN inURN,
String inName,
Language inType,
File inSource,
Properties inParameters,
boolean inRouteOrdersToORS,
ModuleURN inOutputInstance)
throws ModuleCreationException
{
super(inURN,
false);
name = inName;
type = inType;
source = inSource;
parameters = inParameters;
routeOrdersToORS = inRouteOrdersToORS;
outputDestination = inOutputInstance;
MBeanNotificationInfo notifyInfo = new MBeanNotificationInfo(new String[] { AttributeChangeNotification.ATTRIBUTE_CHANGE },
AttributeChangeNotification.class.getName(),
BEAN_ATTRIBUTE_CHANGED.getText());
notificationDelegate = new NotificationBroadcasterSupport(notifyInfo);
}
/**
* Sends the given event to the <code>CEP</code> module specified.
*
* <p>There is no guarantee that the given event can be delivered if the URN does
* not exist or is not started.
*
* @param inCEPModule a <code>ModuleURN</code> value
* @param inEvent an <code>EventBase</code> value
* @throws ModuleException if the event could not be sent
*/
private void sendEventToCEP(ModuleURN inCEPModule,
Event inEvent)
throws ModuleException
{
assert(inCEPModule != null);
assert(inEvent != null);
// see if we have a connection to this module already
DataEmitterSupport establishedConnection = internalDataFlows.get(inCEPModule);
if(establishedConnection == null) {
try(CloseableLock closeableLock = CloseableLock.create(dataFlowLock.writeLock())) {
closeableLock.lock();
// no connection exists yet to the CEP module. create one.
DataFlowID cepFlow = dataFlowSupport.createDataFlow(new DataRequest[] { new DataRequest(getURN(),new InternalRequest(inCEPModule)),new DataRequest(inCEPModule) },
false);
new RequestContainer(cepFlow,
counter.incrementAndGet());
establishedConnection = internalDataFlows.get(inCEPModule);
if(establishedConnection == null) {
StrategyModule.log(LogEventBuilder.warn().withMessage(CANNOT_CREATE_CONNECTION,
String.valueOf(strategy),
inCEPModule).create(),
strategy);
return;
}
}
}
assert(establishedConnection != null);
establishedConnection.send(inEvent);
}
/**
* Confirms that the object attributes are in the state they are expected to be in at the beginning of {@link #preStart()}.
*/
private void assertStateForPreStart()
{
assert(dataFlowSupport != null);
assert(name != null);
assert(!name.isEmpty());
assert(type != null);
if(source != null) {
assert(source.exists());
assert(source.canRead());
}
}
/**
* Confirms that the object attributes are in the state they are expected to be in at the beginning of {@link #receiveData(DataFlowID, Object)}.
*/
private void assertStateForReceiveData()
{
assertStateForPreStart();
assert(strategy != null);
}
/**
* Subscribes the given data requester to the data flow indicated by the request type.
*
* @param inRequest an <code>OutputType</code> value
* @param inSupport a <code>DataEmitterSupport</code> value
*/
private void subscribe(OutputType inRequest,
DataEmitterSupport inSupport)
{
synchronized(subscribers) {
DataRequester requester = new DataRequester(inSupport,
inRequest);
requester.subscribe();
subscribers.put(inSupport.getRequestID(),
requester);
}
}
/**
* Publishes the given <code>Object</code> to the appropriate subscribers.
*
* @param inObject an <code>Object</code> value
*/
private void publish(Object inObject)
{
assert(inObject != null);
if(inObject instanceof FIXOrder ||
inObject instanceof OrderSingle ||
inObject instanceof OrderCancel ||
inObject instanceof OrderReplace) {
ThreadedMetric.event("strategy-OUT"); //$NON-NLS-1$
ordersPublisher.publish(inObject);
} else if(inObject instanceof Suggestion) {
suggestionsPublisher.publish(inObject);
} else if(inObject instanceof Event) {
eventsPublisher.publish(inObject);
} else if(inObject instanceof Notification) {
notificationsPublisher.publish(inObject);
} else if(inObject instanceof String) {
logPublisher.publish(inObject);
}
allPublisher.publish(inObject);
}
/**
* Requests market data according to the given request.
*
* @param inRequest a <code>MarketDataRequest</code> value
* @return an <code>int</code> value
*/
private int doMarketDataRequest(MarketDataRequest inRequest)
{
try(CloseableLock closeableLock = CloseableLock.create(dataFlowLock.writeLock())) {
closeableLock.lock();
// this is the internal request ID that we pass back to the requester so it knows how to refer to this request
final int internalRequestId = counter.incrementAndGet();
// there are two ways to request market data: the first (and preferable) is to use the marketDataManager, if available. if not available, the second method
// is to use the module framework
try {
if(marketDataManager != null) {
// hooray, we've got option #1 working for us, let's do that
long marketDataManagerRequestId = marketDataManager.requestMarketData(inRequest,
new ISubscriber() {
@Override
public boolean isInteresting(Object inData)
{
return true;
}
@Override
public void publishTo(Object inData)
{
if(inData instanceof Event) {
((Event)inData).setSource(internalRequestId);
}
strategy.dataReceived(inData);
}
});
new RequestContainer(marketDataManagerRequestId,
internalRequestId);
} else {
ModuleURN marketDataURN = constructMarketDataUrn(inRequest.getProvider());
SLF4JLoggerProxy.debug(StrategyModule.class,
"{} received a market data request {} for data from {}", //$NON-NLS-1$
strategy,
inRequest,
marketDataURN);
DataFlowID dataFlowID = dataFlowSupport.createDataFlow(new DataRequest[] { new DataRequest(marketDataURN,
inRequest),
new DataRequest(getURN()) },
false);
new RequestContainer(dataFlowID,
internalRequestId);
}
} catch (Exception e) {
StrategyModule.log(LogEventBuilder.warn().withMessage(MARKET_DATA_REQUEST_FAILED,
inRequest)
.withException(e).create(),
strategy);
return 0;
}
return internalRequestId;
}
}
/**
* Sets the event source on the given event.
*
* @param inEvent an <code>Event</code> value
* @param inFlowID a <code>DataFlowID</code> value
*/
private void setEventSource(Event inEvent,
DataFlowID inFlowID)
{
try(CloseableLock closeableLock = CloseableLock.create(dataFlowLock.readLock())) {
closeableLock.lock();
RequestContainer request = getDataRequestBy(inFlowID);
if(request != null) {
inEvent.setSource(request.getInternalRequestId());
}
}
}
/**
* Gets the request container for the given data flow id.
*
* @param inDataFlowId a <code>DataFlowID</code> value
* @return a <code>RequestContainer</code> value or <code>null</code>
*/
private RequestContainer getDataRequestBy(DataFlowID inDataFlowId)
{
return requestsByDataFlowId.get(inDataFlowId);
}
/**
* Gets the request container for the given internal request id.
*
* @param inInternalRequestId an <code>int</code> value
* @return a <code>RequestContainer</code> value or <code>null</code>
*/
private RequestContainer getDataRequestBy(int inInternalRequestId)
{
return requestsByInternalId.get(inInternalRequestId);
}
/**
* Disconnects the strategy from the ORS, if necessary.
*
* If the strategy is not currently connected to the ORS, this method does nothing
*
* @throws ModuleException if the data flow cannot be disconnected
*/
private void disconnectORSRouting()
throws ModuleException
{
SLF4JLoggerProxy.debug(this,
"Breaking connection to ORS"); //$NON-NLS-1$
try(CloseableLock closeableLock = CloseableLock.create(dataFlowLock.writeLock())) {
closeableLock.lock();
if(orsFlow != null) {
RequestContainer container = getDataRequestBy(orsFlow);
if(container != null) {
container.cancel();
}
}
} catch (Exception e) {
SLF4JLoggerProxy.debug(StrategyModule.class,
e,
"Unable to cancel dataflow {} - continuing", //$NON-NLS-1$
orsFlow);
} finally {
orsFlow = null;
}
}
/**
* Connects the strategy to the ORS, if necessary.
*
* If the strategy is already connected to the ORS, this method does nothing
*
* @throws ModuleException if the data flow cannot be established
*/
private void establishORSRouting()
throws ModuleException
{
SLF4JLoggerProxy.debug(this,
"Establishing connection to ORS"); //$NON-NLS-1$
try(CloseableLock closeableLock = CloseableLock.create(dataFlowLock.writeLock())) {
closeableLock.lock();
if(orsFlow == null) {
// no current routing, establish one
orsFlow = dataFlowSupport.createDataFlow(new DataRequest[] { new DataRequest(getURN(),
OutputType.ORDERS),
new DataRequest(ClientModuleFactory.INSTANCE_URN) },
false);
new RequestContainer(orsFlow,
counter.incrementAndGet());
}
}
}
/**
* Request for data that comes from within strategy to strategy.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: StrategyModule.java 16899 2014-05-11 16:03:04Z colin $
* @since 1.0.0
*/
@ClassVersion("$Id: StrategyModule.java 16899 2014-05-11 16:03:04Z colin $")
private static class InternalRequest
{
/**
* the URN of the module that made the original request for data
*/
private final ModuleURN originalRequester;
/**
* Create a new OneShotRequest instance.
*
* @param inOriginalRequester
*/
private InternalRequest(ModuleURN inOriginalRequester)
{
originalRequester = inOriginalRequester;
}
}
/**
* Encapsulates a market data request.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: StrategyModule.java 16899 2014-05-11 16:03:04Z colin $
* @since 2.4.0
*/
@ClassVersion("$Id: StrategyModule.java 16899 2014-05-11 16:03:04Z colin $")
private class RequestContainer
{
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("DataRequest [internalRequestId=").append(internalRequestId); //$NON-NLS-1$
if(dataFlowId == null) {
builder.append(", dataFlowId=").append(dataFlowId); //$NON-NLS-1$
} else {
builder.append(", marketDataRequestId=").append(marketDataRequestId); //$NON-NLS-1$
}
builder.append("]"); //$NON-NLS-1$
return builder.toString();
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
return new HashCodeBuilder().append(internalRequestId).toHashCode();
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj)
{
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof RequestContainer)) {
return false;
}
RequestContainer other = (RequestContainer) obj;
return new EqualsBuilder().append(internalRequestId,other.internalRequestId).isEquals();
}
/**
* Create a new RequestContainer instance.
*
* @param inDataFlowId a <code>DataFlowID</code> value
* @param inInternalRequestId an <code>int</code> value
*/
private RequestContainer(DataFlowID inDataFlowId,
int inInternalRequestId)
{
dataFlowId = inDataFlowId;
marketDataRequestId = null;
internalRequestId = inInternalRequestId;
requestsByDataFlowId.put(inDataFlowId,
this);
requestsByInternalId.put(internalRequestId,
this);
}
/**
* Create a new RequestContainer instance.
*
* @param inMarketDataRequestId a <code>long</code> value
* @param inInternalRequestId an <code>int</code> value
*/
private RequestContainer(long inMarketDataRequestId,
int inInternalRequestId)
{
dataFlowId = null;
marketDataRequestId = inMarketDataRequestId;
internalRequestId = inInternalRequestId;
requestsByInternalId.put(internalRequestId,
this);
}
/**
* Cancels the market data request and removes it from the request collections.
*
* <p>This method requires an external lock.
*/
private void cancel()
{
if(dataFlowId != null) {
try {
dataFlowSupport.cancel(dataFlowId);
} finally {
requestsByDataFlowId.remove(dataFlowId);
}
} else {
try {
if(marketDataManager != null) {
marketDataManager.cancelMarketDataRequest(marketDataRequestId);
}
} finally {
requestsByInternalId.remove(internalRequestId);
}
}
}
/**
* Get the internalRequestId value.
*
* @return an <code>int</code> value
*/
private int getInternalRequestId()
{
return internalRequestId;
}
/**
* module framework data flow ID or <code>null</code>
*/
private final DataFlowID dataFlowId;
/**
* market data manager market data request ID or <code>null</code>
*/
private final Long marketDataRequestId;
/**
* internal request ID value
*/
private final int internalRequestId;
}
/**
* Represents a request for a subscription to data this strategy can emit.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: StrategyModule.java 16899 2014-05-11 16:03:04Z colin $
* @since 1.0.0
*/
@ClassVersion("$Id: StrategyModule.java 16899 2014-05-11 16:03:04Z colin $")
private class DataRequester
implements ISubscriber
{
/**
* the subscriber
*/
private final DataEmitterSupport emitterSupport;
/**
* the type of data request
*/
private final OutputType requestType;
/**
* Create a new DataRequester instance.
*
* @param inEmitterSupport a <code>DataEmitterSupport</code> value
* @param inRequestType an <code>OutputType</code> value
*/
private DataRequester(DataEmitterSupport inEmitterSupport,
OutputType inRequestType)
{
assert(inEmitterSupport != null);
assert(inEmitterSupport.getRequestID() != null);
emitterSupport = inEmitterSupport;
requestType = inRequestType;
}
/**
* Subscribes to the appropriate publisher.
*/
private void subscribe()
{
if(requestType.equals(OutputType.ORDERS)) {
ordersPublisher.subscribe(this);
} else if(requestType.equals(OutputType.SUGGESTIONS)) {
suggestionsPublisher.subscribe(this);
} else if(requestType.equals(OutputType.EVENTS)) {
eventsPublisher.subscribe(this);
} else if(requestType.equals(OutputType.NOTIFICATIONS)) {
notificationsPublisher.subscribe(this);
} else if(requestType.equals(OutputType.LOG)) {
logPublisher.subscribe(this);
} else if(requestType.equals(OutputType.ALL)) {
allPublisher.subscribe(this);
} else {
throw new IllegalArgumentException(); // this is a development-time exception to indicate the logic needs to be expanded because of a new request type
}
}
/* (non-Javadoc)
* @see org.marketcetera.core.publisher.ISubscriber#isInteresting(java.lang.Object)
*/
@Override
public boolean isInteresting(Object inData)
{
return true;
}
/* (non-Javadoc)
* @see org.marketcetera.core.publisher.ISubscriber#publishTo(java.lang.Object)
*/
@Override
public void publishTo(Object inData)
{
emitterSupport.send(inData);
}
/**
* Unsubscribes from the appropriate publisher.
*/
private void unsubscribe()
{
if(requestType.equals(OutputType.ORDERS)) {
ordersPublisher.unsubscribe(this);
} else if(requestType.equals(OutputType.SUGGESTIONS)) {
suggestionsPublisher.unsubscribe(this);
} else if(requestType.equals(OutputType.EVENTS)) {
eventsPublisher.unsubscribe(this);
} else if(requestType.equals(OutputType.NOTIFICATIONS)) {
notificationsPublisher.unsubscribe(this);
} else if(requestType.equals(OutputType.LOG)) {
logPublisher.unsubscribe(this);
} else if(requestType.equals(OutputType.ALL)) {
allPublisher.unsubscribe(this);
} else {
throw new IllegalArgumentException(); // this is a development-time exception to indicate the logic needs to be expanded because of a new request type
}
}
}
/**
* Constructs a <code>Client</code> connection.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: StrategyModule.java 16899 2014-05-11 16:03:04Z colin $
* @since 2.1.0
*/
@ClassVersion("$Id: StrategyModule.java 16899 2014-05-11 16:03:04Z colin $")
static interface ClientFactory
{
/**
* Returns the <code>Client</code> instance to use to connect to the server.
*
* @return a <code>Client</code> value
* @throws ClientInitException if the client could not be created
*/
Client getClient()
throws ClientInitException;
}
/**
* the name of the strategy being run - this name is chosen by the module caller and has no mandatory correlation
* to the contents of the strategy
*/
private final String name;
/**
* the language type of the strategy being run - this is asserted by the module caller
*/
private final Language type;
/**
* the contents of the strategy - contains the code to be executed
*/
private final File source;
/**
* indicates if orders should be routed to the ORS client or not
*/
private boolean routeOrdersToORS;
/**
* the parameters to present to the strategy, may be empty or null. may be null or empty.
*/
private Properties parameters;
/**
* the instanceURN of a destination for outputs, may be null. if non-null, the object contract is to plumb a route from this object to the instance contained herein for all outputs before start.
*/
private ModuleURN outputDestination;
/**
* the publishing engine for orders
*/
private final PublisherEngine ordersPublisher = new PublisherEngine(true);
/**
* the publishing engine for suggestions
*/
private final PublisherEngine suggestionsPublisher = new PublisherEngine(true);
/**
* the publishing engine for events
*/
private final PublisherEngine eventsPublisher = new PublisherEngine(true);
/**
* the publishing engine for notification objects
*/
private final PublisherEngine notificationsPublisher = new PublisherEngine(true);
/**
* the publishing engine for log output
*/
private final PublisherEngine logPublisher = new PublisherEngine(true);
/**
* the publishing engine for all objects
*/
private final PublisherEngine allPublisher = new PublisherEngine(true);
/**
* tracks subscriber objects by requestIDs
*/
private final Map<RequestID,DataRequester> subscribers = new HashMap<RequestID,DataRequester>();
/**
* the strategy object that represents the actual running strategy
*/
private StrategyImpl strategy;
/**
* services for data flow creation
*/
private DataFlowSupport dataFlowSupport;
/**
* the data flow ID of the route to the ORS, if extant
*/
@GuardedBy("dataFlowLock")
private DataFlowID orsFlow;
/**
* default method for connecting to the client
*/
static volatile ClientFactory clientFactory = new ClientFactory() {
@Override
public Client getClient()
throws ClientInitException
{
return ClientManager.getInstance();
}
};
/**
* counter used to guarantee unique identifiers
*/
private static final AtomicInteger counter = new AtomicInteger();
/**
* the collection of data flows created to send data to a specific URN
*/
private final Map<ModuleURN,DataEmitterSupport> internalDataFlows = new HashMap<ModuleURN,DataEmitterSupport>();
/**
* provides JMX notification support
*/
private final NotificationBroadcasterSupport notificationDelegate;
/**
* counter used for JMX notifications
*/
private final AtomicLong jmxNotificationCounter = new AtomicLong();
/**
* provides access to new market data services
*/
private MarketDataManager marketDataManager;
/**
* guards access to data flow structures
*/
private final ReadWriteLock dataFlowLock = new ReentrantReadWriteLock();
/**
* module framework requests by data flow ID
*/
@GuardedBy("dataFlowLock")
private final Map<DataFlowID,RequestContainer> requestsByDataFlowId = Maps.newHashMap();
/**
* market data requests by internal request ID
*/
@GuardedBy("dataFlowLock")
private final Map<Integer,RequestContainer> requestsByInternalId = Maps.newHashMap();
}