org.marketcetera.strategy.StrategyModule.java Source code

Java tutorial

Introduction

Here is the source code for org.marketcetera.strategy.StrategyModule.java

Source

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 16864 2014-03-20 19:39:48Z colin $
 * @since 1.0.0
 */
@ClassVersion("$Id: StrategyModule.java 16864 2014-03-20 19:39:48Z 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 16864 2014-03-20 19:39:48Z colin $
     * @since 1.0.0
     */
    @ClassVersion("$Id: StrategyModule.java 16864 2014-03-20 19:39:48Z 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 16864 2014-03-20 19:39:48Z colin $
     * @since $Release$
     */
    @ClassVersion("$Id: StrategyModule.java 16864 2014-03-20 19:39:48Z 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 16864 2014-03-20 19:39:48Z colin $
     * @since 1.0.0
     */
    @ClassVersion("$Id: StrategyModule.java 16864 2014-03-20 19:39:48Z 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 16864 2014-03-20 19:39:48Z colin $
     * @since 2.1.0
     */
    @ClassVersion("$Id: StrategyModule.java 16864 2014-03-20 19:39:48Z 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();
}