org.marketcetera.photon.internal.marketdata.DataFlowManager.java Source code

Java tutorial

Introduction

Here is the source code for org.marketcetera.photon.internal.marketdata.DataFlowManager.java

Source

package org.marketcetera.photon.internal.marketdata;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;

import org.apache.commons.lang.Validate;
import org.marketcetera.event.MarketDataEvent;
import org.marketcetera.marketdata.Capability;
import org.marketcetera.marketdata.MarketDataRequest;
import org.marketcetera.marketdata.MarketDataRequestBuilder;
import org.marketcetera.module.*;
import org.marketcetera.photon.marketdata.IMarketDataFeed;
import org.marketcetera.trade.Instrument;
import org.marketcetera.util.except.ExceptUtils;
import org.marketcetera.util.misc.ClassVersion;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Sets;
import com.google.inject.BindingAnnotation;

/* $License$ */

/**
 * Base class for data flow managers. This class handles the interactions with the
 * {@link ModuleManager} and implements all the methods required by the IDataFlowManager interface.
 * <p>
 * Subclasses must implement abstract methods to create concrete data items and to update them when
 * data arrives.
 * <p>
 * Long running market data operations are queued in a separate thread using an Executor. This means
 * that there may be a delay between the time, say, {@link #startFlow(Key)} is called and when the
 * associated MDItem starts getting data.
 * 
 * @see IDataFlowManager
 * @param <T>
 *            the model object type being managed
 * @param <K>
 *            the request key being managed
 * @author <a href="mailto:will@marketcetera.com">Will Horn</a>
 * @version $Id$
 * @since 1.5.0
 */
@ClassVersion("$Id$")
abstract class DataFlowManager<T, K extends Key> implements IDataFlowManager<T, K> {

    /**
     * Identifies the {@link Executor} used by this class for Guice binding.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.PARAMETER })
    @BindingAnnotation
    @interface MarketDataExecutor {
    }

    private final ModuleManager mModuleManager;
    private final Map<K, ModuleURN> mSubscribers = new HashMap<K, ModuleURN>();
    private final Map<K, T> mItems = new MapMaker().makeComputingMap(new Function<K, T>() {
        @Override
        public T apply(final K from) {
            return createItem(from);
        }
    });
    private final ImmutableSet<Capability> mRequiredCapabilities;
    private final Executor mMarketDataExecutor;
    private final IMarketDataRequestSupport mMarketDataRequestSupport;
    private ModuleURN mSourceModule;

    /**
     * Constructor.
     * 
     * @param moduleManager
     *            the module manager
     * @param requiredCapabilities
     *            the capabilities this manager requires, cannot be empty
     * @param marketDataExecutor
     *            an executor for long running module operations that
     *            <strong>must</strong> execute tasks sequentially
     * @param marketDataRequestSupport
     *            supports generating market data requests
     * @throws IllegalArgumentException
     *             if any parameter is null, or if requiredCapabilities is empty
     */
    protected DataFlowManager(final ModuleManager moduleManager, final Set<Capability> requiredCapabilities,
            final Executor marketDataExecutor, IMarketDataRequestSupport marketDataRequestSupport) {
        Validate.noNullElements(
                new Object[] { moduleManager, requiredCapabilities, marketDataExecutor, marketDataRequestSupport });
        mModuleManager = moduleManager;
        mRequiredCapabilities = Sets.immutableEnumSet(requiredCapabilities);
        mMarketDataExecutor = marketDataExecutor;
        mMarketDataRequestSupport = marketDataRequestSupport;
    }

    @Override
    public final T getItem(final K key) {
        Validate.notNull(key);
        return mItems.get(key);
    }

    @Override
    public final synchronized void setSourceFeed(final IMarketDataFeed feed) {
        /*
         * This method restarts all modules even if the feed has not changed, in
         * case anything went wrong the previous time.
         */
        ModuleURN module = feed == null ? null : feed.getURN();
        if (mSourceModule != null) {
            for (ModuleURN subscriber : mSubscribers.values()) {
                stopModule(subscriber, false);
            }
        }
        for (K key : mItems.keySet()) {
            resetItem(key);
        }
        mSourceModule = null;
        if (feed != null) {
            Set<Capability> capabilities = feed.getCapabilities();
            if (capabilities.containsAll(mRequiredCapabilities)) {
                mSourceModule = module;
                for (ModuleURN subscriber : mSubscribers.values()) {
                    startModule(subscriber);
                }
            } else {
                Messages.DATA_FLOW_MANAGER_CAPABILITY_UNSUPPORTED.info(this, feed.getName(), capabilities,
                        mRequiredCapabilities);
            }
        }
    }

    private void startModule(final ModuleURN module) {
        assert module != null;
        mMarketDataExecutor.execute(new ReportingRunnable() {
            @Override
            public void doRun() throws Exception {
                if (!mModuleManager.getModuleInfo(module).getState().isStarted()) {
                    mModuleManager.start(module);
                }
            }
        });
    }

    private void stopModule(final ModuleURN module, final boolean delete) {
        assert module != null;
        mMarketDataExecutor.execute(new ReportingRunnable() {
            @Override
            public void doRun() throws Exception {
                if (mModuleManager.getModuleInfo(module).getState().isStarted()) {
                    mModuleManager.stop(module);
                }
                if (delete) {
                    mModuleManager.deleteModule(module);
                }
            }
        });
    }

    private void resetItem(final K key) {
        assert key != null;
        /*
         * This goes into the queue as well since it depends on earlier
         * stopModule operations being completed.
         */
        mMarketDataExecutor.execute(new Runnable() {
            @Override
            public void run() {
                resetItem(key, getItem(key));
            }
        });
    }

    @Override
    public final synchronized void startFlow(final K key) {
        Validate.notNull(key);
        if (mSubscribers.containsKey(key)) {
            return;
        }
        IMarketDataSubscriber subscriber = createSubscriber(key);
        try {
            /*
             * Unlike other module operations, module creation is done
             * synchronously here. This is because 1) we know the from the
             * implementation of the module that it is a quick operation, and 2)
             * it is not dependent on any other potentially queued operations.
             * As a side effect, it allows assertion errors to be reported
             * immediately
             */
            ModuleURN subscriberURN = mModuleManager.createModule(MarketDataReceiverFactory.PROVIDER_URN,
                    subscriber);
            if (mSourceModule != null) {
                startModule(subscriberURN);
            }
            mSubscribers.put(key, subscriberURN);
        } catch (InvalidURNException e) {
            // the provider URN should never be invalid
            throw new AssertionError(e);
        } catch (ModuleCreationException e) {
            // should not happen, we know we are creating it properly
            throw new AssertionError(e);
        } catch (MXBeanOperationException e) {
            // should not happen
            throw new IllegalStateException(e);
        } catch (ModuleException e) {
            // something went wrong creating the module, throw runtime exception
            // since no error handling is supported at this point
            throw new IllegalStateException(e);
        }
    }

    @Override
    public final synchronized void stopFlow(final K key) {
        Validate.notNull(key);
        ModuleURN subscriberURN = mSubscribers.remove(key);
        if (subscriberURN == null) {
            return;
        }
        stopModule(subscriberURN, true);
        resetItem(key);
    }

    @Override
    public final synchronized void restartFlow(final K key) {
        Validate.notNull(key);
        ModuleURN subscriberURN = mSubscribers.get(key);
        if (subscriberURN == null) {
            return;
        }
        stopModule(subscriberURN, false);
        startModule(subscriberURN);
    }

    /**
     * Initializes a {@link MarketDataRequest} for the provided instrument.
     * 
     * @param instrument
     *            the instrument
     * @return a {@link MarketDataRequest} with instrument information
     */
    protected final MarketDataRequestBuilder initializeRequest(Instrument instrument) {
        return mMarketDataRequestSupport.initializeRequest(instrument);
    }

    /**
     * Hook for subclasses to create and initialize the data item for a given
     * key.
     * 
     * @param key
     *            the data key, will not be null
     * @return a data item that will reflect the market data
     */
    abstract protected T createItem(K key);

    /**
     * Hook for subclasses to reset data items to their initial state since the
     * source module is changing.
     * 
     * @param key
     *            the item key
     * @param item
     *            the item to reset
     */
    abstract protected void resetItem(K key, T item);

    /**
     * Hook for subclasses to provide a custom subscriber to handle market data.
     * The internal {@link DataFlowManager.Subscriber} class should be used to
     * respect the manager's source module.
     * 
     * @param key
     *            the data key, will not be null
     * @return a subscriber that will handle the market data
     */
    abstract protected Subscriber createSubscriber(K key);

    private abstract static class ReportingRunnable implements Runnable {
        @Override
        public final void run() {
            try {
                doRun();
            } catch (Exception e) {
                // TODO: This should be more visible in the UI
                ExceptUtils.swallow(e);
            }
        }

        protected abstract void doRun() throws Exception;
    }

    /**
     * Abstract market data subscriber providing the source module.
     */
    @ClassVersion("$Id$")
    protected abstract class Subscriber implements IMarketDataSubscriber {

        @Override
        public ModuleURN getSourceModule() {
            return mSourceModule;
        }

        /**
         * Convenience method for subclasses that encounter unexpected data.
         * 
         * @param data
         *            the unexpected data
         */
        protected final void reportUnexpectedData(final Object data) {
            // CD trying to figure out if we really need this
            //            Messages.DATA_FLOW_MANAGER_UNEXPECTED_DATA.warn(
            //                    DataFlowManager.this, data, getRequest());
        }

        /**
         * Convenience method for subclasses that encounter data with an
         * unexpected id.
         * 
         * @param data
         *            the unexpected data
         */
        protected final void reportUnexpectedMessageId(final Object data) {
            Messages.DATA_FLOW_MANAGER_UNEXPECTED_MESSAGE_ID.warn(DataFlowManager.this, data, getRequest());
        }

        /**
         * Utility method to validates that a {@link MarketDataEvent} has the
         * expected instrument.
         * 
         * @param instrument
         *            the expected instrument
         * @param event
         *            the incoming event
         * @return true if the instruments match, false otherwise
         * @throws IllegalArgumentException
         *             if any parameter is null
         */
        protected final boolean validateInstrument(final Instrument instrument, final MarketDataEvent event) {
            Validate.noNullElements(new Object[] { instrument, event });
            return validateInstrument(instrument, event.getInstrument());
        }

        /**
         * Utility method to validate a received event instrument against
         * an expected one.
         * 
         * @param expected
         *            the expected instrument
         * @param instrument
         *            the instrument on the event
         * @return true if the instruments match, false otherwise
         * @throws IllegalArgumentException
         *             if expected is null
         */
        protected final boolean validateInstrument(final Instrument expected, final Instrument instrument) {
            Validate.notNull(expected);
            return true;
            //            String symbol = instrument.getSymbol();
            //            if(instrument instanceof HasProviderSymbol) {
            //                symbol = ((HasProviderSymbol)instrument).getProviderSymbol();
            //            }
            //            if (expected.getSymbol().equals(symbol)) {
            //                return true;
            //            } else {
            //                Messages.DATA_FLOW_MANAGER_EVENT_INSTRUMENT_MISMATCH.warn(
            //                        DataFlowManager.this, instrument, expected);
            //                return false;
            //            }
        }
    }
}