package org.marketcetera.marketdata; import java.util.*; import java.util.concurrent.*; import org.marketcetera.core.IFeedComponentListener; import org.marketcetera.core.InMemoryIDFactory; import org.marketcetera.core.InternalID; import org.marketcetera.core.NoMoreIDsException; import org.marketcetera.core.publisher.ISubscriber; import org.marketcetera.core.publisher.PublisherEngine; import org.marketcetera.event.AggregateEvent; import org.marketcetera.event.Event; import org.marketcetera.event.EventTranslator; import org.marketcetera.marketdata.MarketDataFeedToken.Status; import org.marketcetera.metrics.ConditionsFactory; import org.marketcetera.metrics.ThreadedMetric; import org.marketcetera.util.log.I18NBoundMessage1P; import org.marketcetera.util.log.I18NBoundMessage3P; import org.marketcetera.util.log.SLF4JLoggerProxy; import org.marketcetera.util.misc.ClassVersion; import org.marketcetera.util.misc.NamedThreadFactory; /* $License$ */ /** * An abstract base class for all market data feeds. * * <p>Contains logic common to all market data feed implementations with * the mechanics of adding/removing symbol/feed component listeners and keeping track of the feed * status. * * <p>Subclasses represent a connection to a specific market data feed. * * <p>The generic types required are defined as follows: * <ul> * <li>T - The token class for this feed</li> * <li>C - The credentials class for this feed</li> * <li>X - The message translator class for this feed</li> * <li>E - The event translator class for this feed</li> * <li>D - The type returned from {@link DataRequestTranslator#fromDataRequest(MarketDataRequest)}.</li> * <li>F - The market data feed type itself</li> * </ul> * * @author andrei@lissovski.org * @author gmiller * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @since 0.5.0 */ @ClassVersion("$Id: AbstractMarketDataFeed.java 16834 2014-02-13 20:40:53Z colin $") //$NON-NLS-1$ public abstract class AbstractMarketDataFeed<T extends AbstractMarketDataFeedToken<F>, C extends MarketDataFeedCredentials, X extends DataRequestTranslator<D>, E extends EventTranslator, D, F extends AbstractMarketDataFeed<?,?,?,?,?,?>> implements MarketDataFeed<T,C> { /** * the id factory used to generate unique IDs within the context of all feeds for this JVM session */ private static final InMemoryIDFactory sIDFactory = new InMemoryIDFactory(0, Long.toString(System.currentTimeMillis())); /** * the singleton instance that aggregates the actual connections to the server */ private static ExecutorService sPool = Executors.newCachedThreadPool(new NamedThreadFactory("AbsMarketDataFeed-")); //$NON-NLS-1$ /** * the status of the feed */ private FeedStatus mFeedStatus = FeedStatus.UNKNOWN; /** * the type of the feed */ private final FeedType mFeedType; /** * the feed ID */ private final InternalID mID; /** * helper object to track the tokens and associated handles from requests and responses */ private final HandleHolder mHandleHolder = new HandleHolder(); /** * provider name associated with the data feed */ private final String mProviderName; /** * publish/subscribe engine to manage feed status changes */ private final PublisherEngine mFeedStatusPublisher = new PublisherEngine(); /** * detailed messages generated by the data feeds */ public final static String DATAFEED_TRACE_MESSAGES = "datafeed.trace"; //$NON-NLS-1$ /** * status messages generated by the data feeds */ public final static String DATAFEED_STATUS_MESSAGES = "datafeed.status"; //$NON-NLS-1$ /** * common key used to create a well-known location for storing an indicator to tell a feed whether to simulate market data or * try for the real thing. the intended use is to set this property in the System environment (accessible via System.getProperty), * but its use is not strictly limited to that by design. */ public final static String MARKETDATA_SIMULATION_KEY = "allowMarketDataSimulation"; //$NON-NLS-1$ /** * Condition used to determine if performance instrumentation should be * recording sampled instrumentation data. */ private static final Callable<Boolean> PUBLISHING_CONDITION = ConditionsFactory.createSamplingCondition(100, "metc.metrics.marketdata.sampling.interval"); //$NON-NLS-1$ /** * Indicates if the feed is allowed to simulate market data if the normal source is not * available. * * @return a <code>boolean</code> value */ private boolean isSimulatedDataAllowed() { return Boolean.getBoolean(MARKETDATA_SIMULATION_KEY); } /** * Gets the next ID in sequence for assigning unique identifiers to market data feed objects. * * @return an <code>InternalID</code> value * @throws NoMoreIDsException if no more IDs are available */ private static InternalID getNextID() throws NoMoreIDsException { return new InternalID(sIDFactory.getNext()); } /** * Create a new <code>AbstractMarketDataFeed</code> instance. * * @param inFeedType a <code>FeedType</code> value * @param inProviderName a <code>String</code> value * @throws NoMoreIDsException if no more ids are available to be assigned to this feed * @throws NullPointerException if the <code>FeedType</code> is null */ protected AbstractMarketDataFeed(FeedType inFeedType, String inProviderName) throws NoMoreIDsException { if(inFeedType == null) { throw new NullPointerException(); } if(inProviderName == null) { throw new NullPointerException(); } mFeedType = inFeedType; mID = getNextID(); mProviderName = inProviderName; mFeedStatus = FeedStatus.OFFLINE; if(isSimulatedDataAllowed()) { SLF4JLoggerProxy.debug(this, "simulated data is allowed"); //$NON-NLS-1$ } else { SLF4JLoggerProxy.debug(this, "simulated data is not allowed"); //$NON-NLS-1$ } } /* (non-Javadoc) * @see org.marketcetera.marketdata.IMarketDataFeed#execute(org.marketcetera.marketdata.AbstractMarketDataFeedCredentials, DataRequest, org.marketcetera.core.publisher.ISubscriber) */ @Override public final T execute(MarketDataFeedTokenSpec inTokenSpec) throws FeedException { // check the status of the feed - if it's not running, the feed isn't // ready to be used if(!getFeedStatus().isRunning()) { throw new FeedException(new I18NBoundMessage1P(Messages.MARKET_DATA_FEED_CANNOT_EXEC_REQUESTS, getFeedStatus())); } // check to see if the asset class is supported by the feed if(!getSupportedAssetClasses().contains(inTokenSpec.getDataRequest().getAssetClass())) { throw new FeedException(new I18NBoundMessage3P(Messages.UNSUPPORTED_ASSET_CLASS, String.valueOf(this), inTokenSpec.getDataRequest().getAssetClass(), inTokenSpec.getDataRequest())); } // these subscribers are all the ones that are interested in the results // of the query we're about to execute - this list may be empty or null ISubscriber[] subscribers = inTokenSpec.getSubscribers(); // the token is used to track the request and its responses // generate a new token for this request T token; try { token = generateToken(inTokenSpec); } catch (Exception e) { throw new FeedException(e, Messages.ERROR_MARKET_DATA_FEED_EXECUTION_FAILED); } // it's possible that some messages won't need subscribers, perhaps if the caller doesn't care // about responses. if the subscriber is null, ignore it. otherwise, set the token to receive // the responses if(subscribers != null) { token.subscribeAll(subscribers); } try { return submitToken(token); } catch (TimeoutException e) { throw new FeedException(e, Messages.ERROR_MARKET_DATA_FEED_EXECUTION_FAILED); } } /* (non-Javadoc) * @see org.marketcetera.marketdata.MarketDataFeed#login(org.marketcetera.marketdata.MarketDataFeedCredentials) */ @Override public final boolean login(C inCredentials) { SLF4JLoggerProxy.debug(this, "Logging in to {}", //$NON-NLS-1$ this); if(isLoggedIn()) { SLF4JLoggerProxy.debug(this, "Already logged in!"); //$NON-NLS-1$ return true; } if(doInitialize() && (isSimulatedDataAllowed() || doLogin(inCredentials))) { SLF4JLoggerProxy.debug(this, "Login successful, setting feed status to \"available\""); //$NON-NLS-1$ setFeedStatus(FeedStatus.AVAILABLE); return true; } SLF4JLoggerProxy.debug(this, "Login failed, setting feed status to \"offline\""); //$NON-NLS-1$ setFeedStatus(FeedStatus.OFFLINE); return false; } /* (non-Javadoc) * @see org.marketcetera.marketdata.MarketDataFeed#logout() */ @Override public final void logout() { if(!isSimulatedDataAllowed()) { doLogout(); } setFeedStatus(FeedStatus.OFFLINE); } /* (non-Javadoc) * @see org.marketcetera.marketdata.IFeedComponent#getFeedStatus() */ public final FeedStatus getFeedStatus() { return mFeedStatus; } /* (non-Javadoc) * @see org.marketcetera.marketdata.IFeedComponent#getFeedType() */ public final FeedType getFeedType() { return mFeedType; } /* (non-Javadoc) * @see org.springframework.context.Lifecycle#isRunning() */ public boolean isRunning() { return getFeedStatus().isRunning(); } /* (non-Javadoc) * @see org.springframework.context.Lifecycle#start() */ public void start() { setFeedStatus(FeedStatus.UNKNOWN); } /* (non-Javadoc) * @see org.springframework.context.Lifecycle#stop() */ public void stop() { // get all the tokens from active queries List<T> tokens = mHandleHolder.getTokens(); for(T token : tokens) { // for each token, suspend the query. this amounts to canceling the query // on the datafeed side but keeping the token in the active list. this // accomplishes two goals: // 1) if the feed is truly stopping and not restarting, we clean up our // connections with the datafeed // 2) if the feed is being restarted, we have a record of all the queries // that were active before SLF4JLoggerProxy.debug(this, "Suspending {}", token); //$NON-NLS-1$ token.setStatus(Status.SUSPENDED); // this performs the cancel on the datafeed side doHandleInvalidation(mHandleHolder.getHandles(token)); } if(!isSimulatedDataAllowed()) { doLogout(); } setFeedStatus(FeedStatus.OFFLINE); } /* (non-Javadoc) * @see org.marketcetera.marketdata.IFeedComponent#getID() */ public final String getID() { return mID.toString(); } /* (non-Javadoc) * @see org.marketcetera.marketdata.IFeedComponent#addFeedComponentListener(org.marketcetera.core.IFeedComponentListener) */ public final void addFeedComponentListener(IFeedComponentListener inListener) { mFeedStatusPublisher.subscribe(new FeedComponentListenerWrapper(inListener, this)); } /* (non-Javadoc) * @see org.marketcetera.marketdata.IFeedComponent#removeFeedComponentListener(org.marketcetera.core.IFeedComponentListener) */ public final void removeFeedComponentListener(IFeedComponentListener inListener) { mFeedStatusPublisher.unsubscribe(new FeedComponentListenerWrapper(inListener, this)); } /* * the following methods must be implemented by the feed subclasses and * represent the contract between subclass and parent */ /** * Generates a token encapsulating the given request. * * <p>The object returned is dedicated to the execution of the given message. * * @param inTokenSpec a <code>MarketDataFeedTokenSpec</code> value encapsulating the data feed request * @return a <code>AbstractMarketDataFeedToken</code> value * @throws FeedException if an error occurs */ protected abstract T generateToken(MarketDataFeedTokenSpec inTokenSpec) throws FeedException; /** * Executes the market data request represented by the passed value. * * <p>The values returned in the handle list must be unique with respect * to the current JVM invocation for this data feed. * * @param inData a <code>D</code> value containing the data returned by * the corresponding {@link DataRequestTranslator}. * @return a <code>List<String></code> value containing the set of * handles to be associated with this request * @throws FeedException if the request cannot be transmitted to the feed * @see DataRequestTranslator#fromDataRequest(MarketDataRequest) */ protected abstract List<String> doMarketDataRequest(D inData) throws FeedException; /** * Returns an instance of {@link DataRequestTranslator} appropriate for this feed. * * <p>The {@link DataRequestTranslator} translates a <code>DataRequest</code> to a data-type * appropriate for this feed. * * @return an <code>X</code> value */ protected abstract X getMessageTranslator(); /** * Determines if there exists an active and valid connection to the feed. * * @return a <code>boolean</code> value */ protected abstract boolean isLoggedIn(); /** * Connects to the feed and supplies the given credentials. * * @param inCredentials a <code>C</code> value * @return a <code>boolean<code> value indicating whether the login * was successful or not */ protected abstract boolean doLogin(C inCredentials); /** * Disconnect from the feed. * @throws InterruptedException if the thread was interrupted during execution */ protected abstract void doLogout(); /** * Cancel the transaction associated with the given handle. * * @param inHandle a <code>String</code> value containing a handle * meaningful to the feed */ protected abstract void doCancel(String inHandle); /** * Returns an instance of {@link EventTranslator} appropriate for this feed. * * <p>The {@link EventTranslator} translates data-types appropriate for this feed * to subclasses of {@link Event}. * * @return an <code>E</code> value */ protected abstract E getEventTranslator(); /* * the following methods *may* be overridden by implementing subclasses but * have default implementations as documented */ /** * Performs any initialization steps necessary before {@link #doLogin(MarketDataFeedCredentials)}. * * <p>This implementation does nothing. * * @return a <code>boolean</code> value indicating if the initialization was valid and * processing may continue */ protected boolean doInitialize() { return true; } /** * Called at the beginning of {@link #doExecute(AbstractMarketDataFeedToken)}. * * <p>This implementation returns <code>true</code>. Subclasses may override this * method to abort an execution if necessary. Return <code>false</code> to abort * the execution. * * @param inToken a <code>T</code> value * @return a <code>boolean</code> value */ protected boolean beforeDoExecute(T inToken) { return true; } /** * Called at the end of {@link #doExecute(AbstractMarketDataFeedToken)}. * * <p>This implementation does nothing. Subclasses may override this * method to implement behavior required at the end of an execution. * This method will always be called, regardless of the success or failure * of the execution. * * @param inToken a <code>T</code> value * @param inHandles a <code>List<String></code> value containing the handles associated with the given token * @param inException a <code>Exception</code> value containing the exception thrown * during execution or <code>null</code> if no exception was thrown */ protected void afterDoExecute(T inToken, List<String> inHandles, Exception inException) { } /** * Gets the number of seconds to wait for an execution to be acknowledged. * * <p>This method returns 60. Subclasses may override this method to return a different value. * The timeout applies to calls to {@link #execute(MarketDataFeedTokenSpec)}. Note that the * feed is not obligated to return data within the time limit, merely to acknowledge log-in and * query submission. * * @return a <code>long</code> value containing the number of seconds to wait before timing out */ protected long getTimeout() { return 60; } /* * the following methods may be called by implementing subclasses but may not * be overridden */ /** * Sets the status of the feed. * * @param inFeedStatus a <code>FeedStatus</code> value * @throws NullPointerException if <code>inFeedStatus</code> is null */ protected final void setFeedStatus(FeedStatus inFeedStatus) { SLF4JLoggerProxy.debug(this, "Setting feed status to {}", //$NON-NLS-1$ inFeedStatus); if(inFeedStatus == null) { throw new NullPointerException(); } synchronized(this) { // if the new status is not the same as the old one, act on it, otherwise ignore it to reduce noise if(!(mFeedStatus.equals(inFeedStatus))) { // if the old status was "off-line" and the new status is "available" then // we need to do something to get the ball back rolling try { if(!mFeedStatus.isRunning() && inFeedStatus.isRunning()) { doReconnectToFeed(); } } finally { // regardless of whether the above succeeds or fails, make sure // that the feed status is set appropriately and the subscribers // are notified mFeedStatus = inFeedStatus; mFeedStatusPublisher.publish(this); notifyAll(); } } } } /** * Registers data received from the feed in association with the * given handle. * * <p>Subclasses should call this method to process data received * from the data feed. * * @param inHandle a <code>MarketDataHandle</code> value * @param inData an <code>Object</code> value containing the data * to be transmitted to subscribers */ protected final void dataReceived(String inHandle, Object inData) { ThreadedMetric.begin(); try { MarketDataHandle mdHandle = compose(inHandle); T token = mHandleHolder.getToken(mdHandle); if(token == null) { Messages.WARNING_MARKET_DATA_FEED_DATA_IGNORED.debug(this, inData); } else { try { E eventTranslator = getEventTranslator(); List<Event> events = eventTranslator.toEvent(inData, inHandle); // events returned may contain aggregate events, which further need to be decomposed // create a list of actual events List<Event> actualEvents = new ArrayList<Event>(); // check the events returned to find aggregate events, if any for(Event event : events) { if(event instanceof AggregateEvent) { AggregateEvent ae = (AggregateEvent)event; actualEvents.addAll(ae.decompose()); } else { actualEvents.add(event); } } ThreadedMetric.event("mdata-translated"); //$NON-NLS-1$ // now publish the complete list of events in the proper order for(Event event : actualEvents) { event.setSource(token); event.setProvider(getProviderName()); token.publish(event); } } catch (Exception e) { Messages.WARNING_MARKET_DATA_FEED_DATA_IGNORED.warn(this, e, inData); } } } finally { ThreadedMetric.end(PUBLISHING_CONDITION); } } /* * the following methods can be called by other package classes */ /** * Cancels the active request represented by the given token. * * <p>Every effort will be made to make sure each query associated with * the given token is canceled. * * @param inToken a <code>T</code> value * @throws NullPointerException if <code>inToken</code> is null */ final void cancel(T inToken) { if(inToken == null) { throw new NullPointerException(); } try { // translate token to handle or handles List<MarketDataHandle> marketDataHandles = mHandleHolder.removeToken(inToken); if(marketDataHandles != null) { // pass handles to subclass to execute cancel for(MarketDataHandle marketDataHandle : marketDataHandles) { try { doCancel(decompose(marketDataHandle)); } catch (Exception e) { Messages.WARNING_MARKET_DATA_FEED_CANNOT_CANCEL_SUBSCRIPTION.warn(this, e, marketDataHandle); } } } } finally { inToken.setStatus(Status.CANCELED); } } /* * the following methods are helper methods for this class */ /** * Performs the execution of the market data request. * * @param inToken a <code>T</code> value * @return a <code>boolean</code> value */ private boolean doExecute(T inToken) { Exception thrownException = null; List<String> handles = new ArrayList<String>(); try { if(!beforeDoExecute(inToken)) { return false; } // translate fix message to specialized type X xlator = getMessageTranslator(); // the request is represented by a request stored on the token MarketDataRequest request = inToken.getTokenSpec().getDataRequest(); // translate the request to an appropriate proprietary format D data = xlator.fromDataRequest(request); if(request instanceof MarketDataRequest) { List<String> handlesReturned = doMarketDataRequest(data); if(handlesReturned != null) { handles.addAll(handlesReturned); } processResponse(handles, inToken); return true; } // Unhandled message type Messages.ERROR_MARKET_DATA_FEED_UNKNOWN_MESSAGE_TYPE.error(this); return false; } catch (Exception e) { thrownException = e; Messages.ERROR_MARKET_DATA_FEED_EXECUTION_FAILED.error(this, e); Messages.ERROR_MARKET_DATA_FEED_EXECUTION_FAILED.error(org.marketcetera.core.Messages.USER_MSG_CATEGORY, e); return false; } finally { afterDoExecute(inToken, handles, thrownException); } } /** * Processes the handles returned from a feed request. * * <p>The given handles will be associated with the given token. Later, when data is returned from the feed via {@link #dataReceived(String, Object)}, * the handles stored here are used to associate the data with the token. * * @param inHandles a <code>List<String></code> value containing the handles returned from the feed to associate with the given token * @param inToken a <code>T</code> value to which to associate the handles */ private void processResponse(List<String> inHandles, T inToken) { if(inHandles == null || inHandles.size() == 0) { Messages.WARNING_MARKET_DATA_FEED_NOT_RETURN_HANDLE.warn(this); } else { mHandleHolder.addHandles(inToken, inHandles); } } /** * Executes the steps necessary when the connection to the feed has been * resumed. */ protected final void doReconnectToFeed() { SLF4JLoggerProxy.debug(this, "Reconnecting to feed({}), resubmitting queries", //$NON-NLS-1$ isLoggedIn() ? "logged in" : "not logged in"); //$NON-NLS-1$ //$NON-NLS-2$ // retrieve all the tokens for active queries Collection<T> tokens = mHandleHolder.getTokens(); // these tokens need to be resubmitted to the feed // the token must still be valid because the various subscribers // will still be listening. the query represented by the token // needs to be resubmitted to the feed and the resulting handles // need to be added to the collection. for(T token : tokens) { // remove old record of this query doHandleInvalidation(mHandleHolder.removeToken(token)); // set status to indicate it is being resubmitted token.setStatus(Status.RESUBMITTING); // newly submit query, allowing its new handles to be added SLF4JLoggerProxy.debug(this, "Resubmitting {}",token); //$NON-NLS-1$ try { submitToken(token); } catch (TimeoutException e) { Messages.ERROR_TOKEN_RESUBMIT_FAILED.error(this, e, token); break; } catch (FeedException e) { Messages.ERROR_TOKEN_RESUBMIT_FAILED.warn(this, e, token); } } } /** * Executes the invalidation of the handles passed. * * <p>The feed will be notified that each handle passed is invalid. * * @param inHandles a <code>List<MarketDataHandle></code> value */ private void doHandleInvalidation(List<MarketDataHandle> inHandles) { for(MarketDataHandle handle : inHandles) { try { doCancel(handle.decompose()); } catch (Exception e) { Messages.WARNING_MARKET_DATA_FEED_CANNOT_CANCEL_SUBSCRIPTION.warn(this, e, handle); } } } /** * Returns a {@link MarketDataHandle} instance encapsulating the given data feed proto-handle. * * @param inSeed a <code>String</code> value containing the proto-handle returned from * a data feed * @return a <code>MarketDataHandle</code> value */ private MarketDataHandle compose(String inSeed) { return new MarketDataHandle(inSeed); } /** * Returns the data feed proto-handle from the given {@link MarketDataHandle}. * * @param inHandle a <code>MarketDataHandle</code> value * @return a <code>String</code> value */ private String decompose(MarketDataHandle inHandle) { return inHandle.decompose(); } /** * Returns the provider name associated with this feed. * * @return a <code>String</code> value */ protected final String getProviderName() { return mProviderName; } /** * Causes the given token to be executed. * * @param inToken a <code>T</code> value * @return a <code>T</code> value * @throws TimeoutException if the execute times out before returning * @throws FeedException if an exception occurs */ private T submitToken(T inToken) throws FeedException, TimeoutException { // construct the object that will be invoked by the ThreadPool - this object is used // to execute the query represented by the token ExecutorThread thread = new ExecutorThread(inToken); try { // this command executes the request using the connector. the connector has all the information it needs // to execute the request because of the token. this command is asynchronous. Future<T> response = sPool.submit(thread); // wait for the response to be returned for a max of 1 minute. this doesn't mean that the results are back yet, just that // the request has been received and acknowledged by the feed service. if you can dig it, it's an // asynchronous request for an asynchronous response. return response.get(getTimeout(), TimeUnit.SECONDS); } catch (TimeoutException e) { throw e; } catch (Exception e) { throw new FeedException(e, Messages.ERROR_MARKET_DATA_FEED_EXECUTION_FAILED); } } /* * the following are private helper classes */ /** * Helper that performs the actual execution in the threadpool. * * <p>This class exists for two reasons: * <ol> * <li>Implement {@link Callable} which includes a public method for * the benefit of the {@link ExecutorService} (threadpool) but * without requiring {@link AbstractMarketDataFeed} * to implement {@link Callable#call()}, which should not be * called by external classes.</li> * <li>Make sure that a specific pair of {@link T} and {@link C} * corresponds to the {@link Callable#call()} method that uses * them</li> * </ol> * * <p>Note that this class is intentionally declared <code>non-static</code> * because it must have access to the parent's <code>T</code> and <code>C</code> * types and must call several non-static methods on the parent. * * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @version $Id: AbstractMarketDataFeed.java 16834 2014-02-13 20:40:53Z colin $ * @since 0.5.0 */ @ClassVersion("$Id: AbstractMarketDataFeed.java 16834 2014-02-13 20:40:53Z colin $") //$NON-NLS-1$ private class ExecutorThread implements Callable<T> { /** * the token representing the request and its responses */ private final T mToken; /** * Create a new ExecutorThread instance. * * @param inToken a <code>T</code> value */ private ExecutorThread(T inToken) { mToken = inToken; } /* (non-Javadoc) * @see java.util.concurrent.Callable#call() */ public T call() throws Exception { T token = mToken; token.setStatus(MarketDataFeedToken.Status.RUNNING); if(!isLoggedIn()) { // bail out expressing sadness token.setStatus(MarketDataFeedToken.Status.LOGIN_FAILED); return token; } // feed is logged in // do any initialization required boolean succeeded = false; try { succeeded = doInitialize(); } catch (Exception e) { Messages.WARNING_MARKET_DATA_FEED_CANNOT_INITIALIZE.warn(AbstractMarketDataFeed.DATAFEED_STATUS_MESSAGES, e); succeeded = false; } if(!succeeded) { token.setStatus(MarketDataFeedToken.Status.INITIALIZATION_FAILED); return token; } // feed should be ready for commands // execute command, wait for status response, not responses to the actual command succeeded = false; try { succeeded = doExecute(token); } catch (Exception e) { Messages.WARNING_MARKET_DATA_FEED_CANNOT_EXEC_COMMAND.warn(AbstractMarketDataFeed.DATAFEED_STATUS_MESSAGES, e); succeeded = false; } if(!succeeded) { token.setStatus(MarketDataFeedToken.Status.EXECUTION_FAILED); return token; } token.setStatus(MarketDataFeedToken.Status.ACTIVE); return token; } } /** * Encapsulates the handles and tokens collections. * * <p>Note that this class is declared non-static intentionally in order * to use the parent class's generic types. * * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @version $Id: AbstractMarketDataFeed.java 16834 2014-02-13 20:40:53Z colin $ * @since 0.5.0 */ @ClassVersion("$Id: AbstractMarketDataFeed.java 16834 2014-02-13 20:40:53Z colin $") //$NON-NLS-1$ private class HandleHolder { /** * stores client response handles by token - note that these two collections must be kept in sync */ private final Map<T,List<MarketDataHandle>> mHandlesByToken = new HashMap<T,List<MarketDataHandle>>(); /** * stores tokens by handle - note that these two collections must be kept in sync */ private final Map<MarketDataHandle,T> mTokensByHandle = new HashMap<MarketDataHandle,T>(); /** * object used for synchronization lock for these two collections */ private final Object mLock = new Object(); /** * Create a new HandleHolder instance. */ private HandleHolder() { } /** * Records the given handles as associated to the given token. * * @param inToken a <code>T</code> value * @param inHandles a <code>List<String></code> value */ private void addHandles(T inToken, List<String> inHandles) { // by convention, synchronization for mHandlesByToken and mTokensByHandle // is performed on mLock to avoid deadlock - do not access // mTokensByHandle or mHandlesByToken without synchronizing mLock synchronized(mLock) { List<MarketDataHandle> marketDataHandles = mHandlesByToken.get(inToken); if(marketDataHandles == null){ marketDataHandles = new ArrayList<MarketDataHandle>(); } for(String handle : inHandles) { MarketDataHandle mdHandle = compose(handle); marketDataHandles.add(mdHandle); mTokensByHandle.put(mdHandle, inToken); } mHandlesByToken.put(inToken, marketDataHandles); } } /** * Returns the token associated with the given handle. * * @param inMarketDataHandle a <code>MarketDataHandle</code> value * @return a <code>T</code> value or null if no token is associated * with the given handle */ private T getToken(MarketDataHandle inMarketDataHandle) { // by convention, synchronization for mHandlesByToken and mTokensByHandle // is performed on mLock to avoid deadlock - do not access // mTokensByHandle or mHandlesByToken without synchronizing mLock synchronized(mLock) { return mTokensByHandle.get(inMarketDataHandle); } } /** * Removes the given token and its handles from the handle list. * * @param inToken a <code>T</code> value * @return a <code>List<MarketDataHandle></code> value containing the handles * associated with the token */ private List<MarketDataHandle> removeToken(T inToken) { List<MarketDataHandle> handles; // by convention, synchronization for mHandlesByToken and mTokensByHandle // is performed on mLock to avoid deadlock - do not access // mTokensByHandle or mHandlesByToken without synchronizing mLock synchronized(mLock) { handles = mHandlesByToken.remove(inToken); if(handles == null) { return new ArrayList<MarketDataHandle>(); } for(MarketDataHandle handle : handles) { mTokensByHandle.remove(handle); } } return handles; } /** * Gets the handles associated with the given token. * * @param inToken a <code>T</code> value * @return a <code>List<MarketDataHandle></code> value */ private List<MarketDataHandle> getHandles(T inToken) { synchronized(mLock) { List<MarketDataHandle> handles = mHandlesByToken.get(inToken); if(handles == null) { handles = new ArrayList<MarketDataHandle>(); } return handles; } } /** * Returns all the tokens in no particular order. * * @return a <code>Collection<T></code> value */ private List<T> getTokens() { synchronized(mLock) { return new ArrayList<T>(mTokensByHandle.values()); } } } /** * A unique handle to associate data feed requests with responses. * * <p>The handle created is guaranteed to be unique within the scope of all * data feeds in the current JVM run iff: * <ol> * <li>all proto-handles returned by {@link AbstractMarketDataFeed#doMarketDataRequest(Object)} * are unique within the scope of the relevant feed in the current JVM run</li> * <li>the set of values returned by {@link IMarketDataFeedFactory#getProviderName()} from all * data feeds contains no duplicates</li> * </ol> * * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @version $Id: AbstractMarketDataFeed.java 16834 2014-02-13 20:40:53Z colin $ * @since 0.5.0 */ @ClassVersion("$Id: AbstractMarketDataFeed.java 16834 2014-02-13 20:40:53Z colin $") //$NON-NLS-1$ private class MarketDataHandle { /** * the handle value */ private final String mHandle; /** * the handle from the data feed itself */ private final String mProtoHandle; /** * Create a new MarketDataHandle instance. * * @param inHandle a <code>String</code> value containing a value * meaningful to the originating data feed which can be used * to refer to a unique data feed request * @throws NullPointerException if <code>inHandle</code> is null */ private MarketDataHandle(String inHandle) { if(inHandle == null) { throw new NullPointerException(); } mProtoHandle = inHandle; mHandle = String.format("%s-%s", //$NON-NLS-1$ getProviderName(), inHandle); } /** * Returns the proto-handle used to originally create this object. * * @return a <code>String</code> value */ private String decompose() { return new String(mProtoHandle); } /* (non-Javadoc) * @see java.lang.Object#toString() */ public String toString() { return new String(mHandle); } /* (non-Javadoc) * @see java.lang.Object#hashCode() */ public int hashCode() { final int PRIME = 31; int result = 1; result = PRIME * result + ((mHandle == null) ? 0 : mHandle.hashCode()); return result; } /* (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; @SuppressWarnings("unchecked") final MarketDataHandle other = (MarketDataHandle) obj; if (mHandle == null) { if (other.mHandle != null) return false; } else if (!mHandle.equals(other.mHandle)) return false; return true; } } /** * Wraps the {@link IFeedComponentListener} for this feed. * * <p>The wrapper translates {@link ISubscriber} methods to * the {@link IFeedComponentListener} objects. * * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @version $Id: AbstractMarketDataFeed.java 16834 2014-02-13 20:40:53Z colin $ * @since 0.5.0 */ @ClassVersion("$Id: AbstractMarketDataFeed.java 16834 2014-02-13 20:40:53Z colin $") //$NON-NLS-1$ private static class FeedComponentListenerWrapper implements ISubscriber { /** * the feed component listener to which to transmit feed status updates */ private final IFeedComponentListener mListener; /** * the feed component (data feed) whose status is changing */ private final IFeedComponent mParent; /** * Create a new FeedComponentListenerWrapper instance. * * @param inListener an <code>IFeedComponentListener</code> value * @param inParent an <code>IFeedComponent</code> value * @throws NullPointerException if either the listener or parent are null */ private FeedComponentListenerWrapper(IFeedComponentListener inListener, IFeedComponent inParent) { if(inListener == null || inParent == null) { throw new NullPointerException(); } mListener = inListener; mParent = inParent; } /* (non-Javadoc) * @see org.marketcetera.core.publisher.ISubscriber#isInteresting(java.lang.Object) */ public boolean isInteresting(Object inData) { return true; } /* (non-Javadoc) * @see org.marketcetera.core.publisher.ISubscriber#publishTo(java.lang.Object) */ public void publishTo(Object inData) { mListener.feedComponentChanged(mParent); } /* (non-Javadoc) * @see java.lang.Object#hashCode() */ public int hashCode() { final int PRIME = 31; int result = 1; result = PRIME * result + ((mListener == null) ? 0 : mListener.hashCode()); return result; } /* (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final FeedComponentListenerWrapper other = (FeedComponentListenerWrapper) obj; if (mListener == null) { if (other.mListener != null) return false; } else if (!mListener.equals(other.mListener)) return false; return true; } } }