package org.marketcetera.marketdata.core.provider; import java.util.*; import java.util.concurrent.BlockingDeque; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.ThreadSafe; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; import org.marketcetera.core.publisher.ISubscriber; import org.marketcetera.event.Event; import org.marketcetera.event.EventType; import org.marketcetera.event.HasEventType; import org.marketcetera.marketdata.Capability; import org.marketcetera.marketdata.Content; import org.marketcetera.marketdata.MarketDataRequest; import org.marketcetera.marketdata.core.MarketDataProvider; import org.marketcetera.marketdata.core.ProviderStatus; import org.marketcetera.marketdata.core.cache.MarketDataCache; import org.marketcetera.marketdata.core.manager.MarketDataException; import org.marketcetera.marketdata.core.manager.MarketDataProviderNotAvailable; import org.marketcetera.marketdata.core.manager.MarketDataProviderRegistry; import org.marketcetera.marketdata.core.manager.MarketDataRequestFailed; import org.marketcetera.marketdata.core.request.MarketDataRequestAtom; import org.marketcetera.marketdata.core.request.MarketDataRequestToken; import org.marketcetera.trade.Instrument; import org.marketcetera.util.log.I18NBoundMessage2P; import org.marketcetera.util.log.SLF4JLoggerProxy; import org.marketcetera.util.misc.ClassVersion; import org.springframework.context.Lifecycle; import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; /* $License$ */ /** * Provides common behavior for market data providers. * * <p>To create a market data provider, extend this class. * * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @version $Id: AbstractMarketDataProvider.java 16912 2014-05-16 23:35:10Z colin $ * @since 2.4.0 */ @ThreadSafe @ClassVersion("$Id: AbstractMarketDataProvider.java 16912 2014-05-16 23:35:10Z colin $") public abstract class AbstractMarketDataProvider implements MarketDataProvider,MarketDataCache { /* (non-Javadoc) * @see org.marketcetera.marketdata.cache.MarketdataCache#getSnapshot(org.marketcetera.core.trade.Instrument, org.marketcetera.marketdata.Content) */ @Override public Event getSnapshot(Instrument inInstrument, Content inContent) { Lock snapshotLock = marketdataLock.readLock(); try { snapshotLock.lockInterruptibly(); MarketdataCacheElement cachedData = cachedMarketdata.get(inInstrument); if(cachedData != null) { return cachedData.getSnapshot(inContent); } return null; } catch (InterruptedException e) { org.marketcetera.marketdata.core.Messages.UNABLE_TO_ACQUIRE_LOCK.error(this); stop(); throw new MarketDataRequestFailed(e); } finally { snapshotLock.unlock(); } } /* (non-Javadoc) * @see org.springframework.context.Lifecycle#start() */ @Override @PostConstruct public synchronized void start() { if(isRunning()) { stop(); } try { doStart(); totalRequests = 0; totalEvents = 0; instrumentsBySymbol.clear(); cachedMarketdata.clear(); notifications.clear(); requestsByInstrument.clear(); requestsByAtom.clear(); requestsBySymbol.clear(); notifier = new EventNotifier(); notifier.start(); running.set(true); setFeedStatus(ProviderStatus.AVAILABLE); } catch (Exception e) { setFeedStatus(ProviderStatus.ERROR); throw new MarketDataProviderStartFailed(e); } } /* (non-Javadoc) * @see org.springframework.context.Lifecycle#stop() */ @Override @PreDestroy public synchronized void stop() { if(!isRunning()) { return; } try { doStop(); setFeedStatus(ProviderStatus.OFFLINE); } catch (Exception e) { setFeedStatus(ProviderStatus.ERROR); } finally { notifier.stop(); instrumentsBySymbol.clear(); cachedMarketdata.clear(); notifications.clear(); requestsByInstrument.clear(); requestsByAtom.clear(); requestsBySymbol.clear(); running.set(false); } } /* (non-Javadoc) * @see org.springframework.context.Lifecycle#isRunning() */ @Override public boolean isRunning() { return running.get(); } /* (non-Javadoc) * @see org.marketcetera.marketdata.provider.MarketDataProvider#requestMarketData(org.marketcetera.marketdata.request.MarketDataRequestToken, org.marketcetera.api.systemmodel.Subscriber) */ @Override public void requestMarketData(MarketDataRequestToken inRequestToken) { if(!isRunning()) { throw new MarketDataProviderNotAvailable(); } Set<MarketDataRequestAtom> atoms = explodeRequest(inRequestToken.getRequest()); totalRequests += atoms.size(); SLF4JLoggerProxy.debug(this, "Received market data request {}, exploded to {}", //$NON-NLS-1$ inRequestToken, atoms); Lock marketdataRequestLock = marketdataLock.writeLock(); try { marketdataRequestLock.lockInterruptibly(); } catch (InterruptedException e) { org.marketcetera.marketdata.core.Messages.UNABLE_TO_ACQUIRE_LOCK.error(this); stop(); throw new MarketDataRequestFailed(e); } SLF4JLoggerProxy.trace(this, "Acquired lock"); //$NON-NLS-1$ try { mapRequestToInstruments(inRequestToken); for(MarketDataRequestAtom atom : atoms) { if(requestsByAtom.containsKey(atom)) { SLF4JLoggerProxy.debug(this, "Already requested {}, adding to reference count", atom); Instrument snapshotInstrument = instrumentsBySymbol.get(atom.getSymbol()); if(snapshotInstrument == null) { SLF4JLoggerProxy.warn(this, "Symbol {} not yet mapped, cannot send snapshot", atom.getSymbol()); } else { Event snapshotEvent = getSnapshot(snapshotInstrument, atom.getContent()); if(snapshotEvent instanceof HasEventType) { HasEventType eventTypeSnapshot = (HasEventType)snapshotEvent; eventTypeSnapshot.setEventType(EventType.SNAPSHOT_FINAL); } if(snapshotEvent != null) { SLF4JLoggerProxy.debug(this, "Sending snapshot: {}", snapshotEvent); if(inRequestToken.getSubscriber() != null) { inRequestToken.getSubscriber().publishTo(snapshotEvent); } } else { SLF4JLoggerProxy.debug(this, "No snapshot for {}", atom); } } requestsByAtom.put(atom, inRequestToken); requestsBySymbol.put(atom.getSymbol(), inRequestToken); } else { Capability requiredCapability = necessaryCapabilities.get(atom.getContent()); if(requiredCapability == null) { org.marketcetera.marketdata.core.Messages.UNKNOWN_MARKETDATA_CONTENT.error(this, atom.getContent()); throw new UnsupportedOperationException(org.marketcetera.marketdata.core.Messages.UNKNOWN_MARKETDATA_CONTENT.getText(atom.getContent())); } Set<Capability> capabilities = getCapabilities(); if(!capabilities.contains(requiredCapability)) { org.marketcetera.marketdata.core.Messages.UNSUPPORTED_MARKETDATA_CONTENT.error(this, atom.getContent(), capabilities.toString()); throw new MarketDataRequestFailed(new I18NBoundMessage2P(org.marketcetera.marketdata.core.Messages.UNSUPPORTED_MARKETDATA_CONTENT, atom.getContent(), capabilities.toString())); } requestsByAtom.put(atom, inRequestToken); requestsBySymbol.put(atom.getSymbol(), inRequestToken); SLF4JLoggerProxy.debug(this, "Requesting {}", atom); doMarketDataRequest(inRequestToken.getRequest(), atom); } } } catch (Exception e) { try { cancelMarketDataRequest(inRequestToken); } catch (Exception ignored) {} org.marketcetera.marketdata.core.Messages.MARKETDATA_REQUEST_FAILED.warn(this, e); if(e instanceof MarketDataException) { throw (MarketDataException)e; } throw new MarketDataRequestFailed(e); } finally { marketdataRequestLock.unlock(); SLF4JLoggerProxy.trace(this, "Lock released"); //$NON-NLS-1$ } } /* (non-Javadoc) * @see org.marketcetera.marketdata.provider.MarketDataProvider#cancelMarketDataRequest(org.marketcetera.marketdata.request.MarketDataRequestToken) */ @Override public void cancelMarketDataRequest(MarketDataRequestToken inRequestToken) { // TODO re-exploding the request might cause problems if the request itself changed, better to associate the token ID // with a set of atoms Lock cancelLock = marketdataLock.writeLock(); try { cancelLock.lockInterruptibly(); Set<MarketDataRequestAtom> atoms = explodeRequest(inRequestToken.getRequest()); for(MarketDataRequestAtom atom : atoms) { Collection<MarketDataRequestToken> symbolRequests = requestsByAtom.get(atom); if(symbolRequests != null) { symbolRequests.remove(inRequestToken); if(symbolRequests.isEmpty()) { doCancel(atom); } } Collection<MarketDataRequestToken> requests = requestsBySymbol.get(atom.getSymbol()); if(requests != null) { requests.remove(inRequestToken); } Instrument mappedInstrument = instrumentsBySymbol.get(atom.getSymbol()); if(mappedInstrument != null) { Collection<MarketDataRequestToken> instrumentRequests = requestsByInstrument.get(mappedInstrument); if(instrumentRequests != null) { instrumentRequests.remove(inRequestToken); if(instrumentRequests.isEmpty()) { // no more requests for this instrument, which means this instrument will no longer be updated - clear the cache for it cachedMarketdata.remove(mappedInstrument); } } } } } catch (InterruptedException e) { org.marketcetera.marketdata.core.Messages.UNABLE_TO_ACQUIRE_LOCK.error(this); stop(); } finally { cancelLock.unlock(); } } /* (non-Javadoc) * @see org.marketcetera.marketdata.MarketDataProvider#getFeedStatus() */ @Override public ProviderStatus getProviderStatus() { return status; } /** * Sets the providerRegistry value. * * @param inProviderRegistry a <code>MarketDataProviderRegistry</code> value */ public void setProviderRegistry(MarketDataProviderRegistry inProviderRegistry) { providerRegistry = inProviderRegistry; } /* (non-Javadoc) * @see org.marketcetera.marketdata.provider.MarketDataProviderMXBean#getTotalRequests() */ @Override public int getTotalRequests() { return totalRequests; } /* (non-Javadoc) * @see org.marketcetera.marketdata.provider.MarketDataProviderMXBean#getActiveRequests() */ @Override public int getActiveRequests() { Lock marketdataQueryLock = marketdataLock.readLock(); try { marketdataQueryLock.lockInterruptibly(); } catch (InterruptedException e) { org.marketcetera.marketdata.core.Messages.UNABLE_TO_ACQUIRE_LOCK.error(this); throw new MarketDataRequestFailed(e); } try { return requestsByAtom.size(); } finally { marketdataQueryLock.unlock(); } } /* (non-Javadoc) * @see org.marketcetera.marketdata.provider.MarketDataProviderMXBean#getTotalEvents() */ @Override public int getTotalEvents() { return totalEvents; } /** * Indicates that the given events have been received by the provider and should be sent to interested subscribers. * * @param inContent a <code>Content</code> value * @param inInstrument an <code>Instrument</code> value * @param inEvents an <code>Event[]</code> value */ protected void publishEvents(Content inContent, Instrument inInstrument, Event...inEvents) { // TODO validation: make sure each event has the proper content and instrument (don't do this every time, just if the provider requests validation) // TODO validation: make sure each instrument has a mapping totalEvents += inEvents.length; notifications.add(new EventNotification(inContent, inInstrument, inEvents)); } /** * Creates a link between the given symbol and the given instrument. * * @param inSymbol a <code>String</code> value * @param inInstrument an <code>Instrument</code> value */ protected void addSymbolMapping(String inSymbol, Instrument inInstrument) { SLF4JLoggerProxy.debug(this, "Adding symbol mapping: {} -> {}", inSymbol, inInstrument); Lock symbolMappingLock = marketdataLock.writeLock(); try { symbolMappingLock.lockInterruptibly(); instrumentsBySymbol.put(inSymbol, inInstrument); Collection<MarketDataRequestToken> tokens = requestsBySymbol.get(inSymbol); for(MarketDataRequestToken token : tokens) { requestsByInstrument.put(inInstrument, token); } } catch (InterruptedException e) { org.marketcetera.marketdata.core.Messages.UNABLE_TO_ACQUIRE_LOCK.error(this); stop(); } finally { symbolMappingLock.unlock(); } } /** * * * <p>This method requires an external write-lock on {@link #requestsByInstrument} and * an external read-lock on {@link #instrumentsBySymbol}. * * @param inToken */ private void mapRequestToInstruments(MarketDataRequestToken inToken) { for(String symbol : inToken.getRequest().getSymbols()) { Instrument instrument = instrumentsBySymbol.get(symbol); if(instrument != null) { requestsByInstrument.put(instrument, inToken); } } for(String symbol : inToken.getRequest().getUnderlyingSymbols()) { Instrument instrument = instrumentsBySymbol.get(symbol); if(instrument != null) { requestsByInstrument.put(instrument, inToken); } } } /** * Sets the feed status value. * * @param inNewStatus a <code>FeedStatus</code> value */ protected void setFeedStatus(ProviderStatus inNewStatus) { if(inNewStatus != status) { status = inNewStatus; if(providerRegistry != null) { providerRegistry.setStatus(this, inNewStatus); } } } /** * Indicates if the provider requests additional validation on the data it produces. * * <p>Subclasses <em>may</em> override this method to increase validation on its generated * event stream. Validation has a minor negative impact on latency. Subclasses should return * <code>true</code> during the development phase but should likely return <code>false</code> * for production. The default returned value is <code>false</code>. * * @return a <code>boolean</code> value */ protected boolean doValidation() { return false; } /** * Starts the market data provider. */ protected abstract void doStart(); /** * Stops the market data provider. */ protected abstract void doStop(); /** * Cancels the market data request represented by the given request atom. * * @param inAtom a <code>MarketDataRequestAtom</code> value */ protected abstract void doCancel(MarketDataRequestAtom inAtom); /** * Indicates to the market data provider that it should request market data for the given * <code>MarketDataRequestAtom</code>. * * <p>Note that the overall <code>MarketDataRequest</code> is provided, and can be used * for reference, but the provider should respond to the given <code>MarketDataRequestAtom</code>. * * @param inCompleteRequest a <code>MarketDataRequest</code> value * @param inRequestAtom a <code>MarketDataRequestAtom</code> value * @throws InterruptedException if the request cannot be executed */ protected abstract void doMarketDataRequest(MarketDataRequest inCompleteRequest, MarketDataRequestAtom inRequestAtom) throws InterruptedException; /** * Gets the distinct market data request atoms from the given request. * * @param inRequest a <code>MarketDataRequest</code> value * @return a <code>Set<MarketDataRequestAtomg></code> value */ private Set<MarketDataRequestAtom> explodeRequest(MarketDataRequest inRequest) { Set<MarketDataRequestAtom> atoms = new LinkedHashSet<MarketDataRequestAtom>(); if(inRequest.getSymbols().isEmpty()) { for(String underlyingSymbol : inRequest.getUnderlyingSymbols()) { for(Content content : inRequest.getContent()) { atoms.add(new MarketDataRequestAtomImpl(underlyingSymbol, inRequest.getExchange(), content, true)); } } } else { for(String symbol : inRequest.getSymbols()) { for(Content content : inRequest.getContent()) { atoms.add(new MarketDataRequestAtomImpl(symbol, inRequest.getExchange(), content, false)); } } } return atoms; } /** * Represents a single market data request item. * * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @version $Id: AbstractMarketDataProvider.java 16912 2014-05-16 23:35:10Z colin $ * @since 2.4.0 */ @Immutable private static class MarketDataRequestAtomImpl implements MarketDataRequestAtom { /* (non-Javadoc) * @see org.marketcetera.marketdata.request.MarketDataRequestAtom#getSymbol() */ @Override public String getSymbol() { return symbol; } /* (non-Javadoc) * @see org.marketcetera.marketdata.request.MarketDataRequestAtom#getExchange() */ @Override public String getExchange() { return exchange; } /* (non-Javadoc) * @see org.marketcetera.marketdata.request.MarketDataRequestAtom#isUnderlyingSymbol() */ @Override public boolean isUnderlyingSymbol() { return isUnderlyingSymbol; } /* (non-Javadoc) * @see org.marketcetera.marketdata.provider.MarketDataRequestAtom#getContent() */ @Override public Content getContent() { return content; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(content).append(" : ").append(symbol); //$NON-NLS-1$ if(exchange != null) { builder.append(" : ").append(exchange); //$NON-NLS-1$ } if(isUnderlyingSymbol) { builder.append(" (underlying)"); //$NON-NLS-1$ } return builder.toString(); } /* (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return new HashCodeBuilder().append(content).append(symbol).append(exchange).append(isUnderlyingSymbol).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 MarketDataRequestAtomImpl)) { return false; } MarketDataRequestAtomImpl other = (MarketDataRequestAtomImpl) obj; return new EqualsBuilder().append(symbol,other.symbol) .append(exchange,other.exchange) .append(content,other.content) .append(isUnderlyingSymbol,other.isUnderlyingSymbol).isEquals(); } /** * Create a new MarketDataRequestAtomImpl instance. * * @param inSymbol a <code>String</code> value * @param inExchange a <code>String</code> value or <code>null</code> * @param inContent a <code>Content</code> value * @param inIsUnderlyingSymbol a <code>boolean</code> value */ private MarketDataRequestAtomImpl(String inSymbol, String inExchange, Content inContent, boolean inIsUnderlyingSymbol) { symbol = inSymbol; exchange = inExchange; content = inContent; isUnderlyingSymbol = inIsUnderlyingSymbol; } /** * symbol value, may be a symbol, an underlying symbol, or a symbol fragment */ private final String symbol; /** * exchange value or <code>null</code> */ private final String exchange; /** * indicates if the symbol is supposed to be a symbol or an underlying symbol */ private final boolean isUnderlyingSymbol; /** * content value of the request */ private final Content content; } /** * Processes events returned by the provider and publishes them to interested subscribers. * * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @version $Id: AbstractMarketDataProvider.java 16912 2014-05-16 23:35:10Z colin $ * @since 2.4.0 */ @ClassVersion("$Id: AbstractMarketDataProvider.java 16912 2014-05-16 23:35:10Z colin $") private class EventNotifier implements Runnable, Lifecycle { /* (non-Javadoc) * @see java.lang.Runnable#run() */ @Override public void run() { try { Collection<MarketDataRequestToken> requests = new ArrayList<MarketDataRequestToken>(); while(keepAlive.get()) { running.set(true); EventNotification notification = notifications.take(); Event[] events = notification.events; if(events != null) { // sort out where to apply these events. the key to the cached market data is the instrument Instrument eventInstrument = notification.instrument; // there is at least one event to process. let the market data cache process each event MarketdataCacheElement marketdataCache = cachedMarketdata.get(eventInstrument); if(marketdataCache == null) { marketdataCache = new MarketdataCacheElement(eventInstrument); cachedMarketdata.put(eventInstrument, marketdataCache); } // we now have the market data cache object to use - give it the incoming events Deque<Event> outgoingEvents = Lists.newLinkedList(marketdataCache.update(notification.content, events)); // find subscribers to this instrument requests.clear(); Lock requestLock = marketdataLock.readLock(); try { requestLock.lockInterruptibly(); // defensive copy to avoid chance of CME if a cancel is called while the processing is ongoing requests.addAll(requestsByInstrument.get(eventInstrument)); } finally { requestLock.unlock(); } boolean isSnapshot = false; // determine if we're dealing with a snapshot or update for(Event outgoingEvent : outgoingEvents) { if(outgoingEvent instanceof HasEventType) { HasEventType hasEventType = (HasEventType)outgoingEvent; if(hasEventType.getEventType() == EventType.SNAPSHOT_FINAL || hasEventType.getEventType() == EventType.SNAPSHOT_PART) { isSnapshot = true; break; } } } // now, set the appropriate flag HasEventType lastEvent = null; for(Event outgoingEvent : outgoingEvents) { if(outgoingEvent instanceof HasEventType) { HasEventType hasEventType = (HasEventType)outgoingEvent; lastEvent = hasEventType; hasEventType.setEventType(isSnapshot?EventType.SNAPSHOT_PART:EventType.UPDATE_PART); } } if(lastEvent != null) { lastEvent.setEventType(isSnapshot?EventType.SNAPSHOT_FINAL:EventType.UPDATE_FINAL); } SLF4JLoggerProxy.trace("events.publishing", "Publishing {} to {}", outgoingEvents, requests); for(MarketDataRequestToken requestToken : requests) { // for each subscriber, determine if the request contents justifies the update if(requestToken.getRequest().getContent().contains(notification.content)) { // enclose the "publishTo" in a try/catch because we're ceding control to unknown code and // we don't want a misbehaving subscriber to break the market data mechanism try { for(Event outgoingEvent : outgoingEvents) { outgoingEvent.setSource(requestToken.getId()); outgoingEvent.setProvider(getProviderName()); ISubscriber subscriber = requestToken.getSubscriber(); if(subscriber != null && subscriber.isInteresting(outgoingEvent)) { subscriber.publishTo(outgoingEvent); } } } catch (Exception e) { org.marketcetera.marketdata.core.Messages.EVENT_NOTIFICATION_FAILED.warn(AbstractMarketDataProvider.this, e, outgoingEvents, requestToken.getSubscriber()); } } } } } } catch (InterruptedException e) { } finally { SLF4JLoggerProxy.debug(AbstractMarketDataProvider.this, "Event notifier for {} shutting down", //$NON-NLS-1$ getProviderName()); running.set(false); } } /* (non-Javadoc) * @see org.springframework.context.Lifecycle#start() */ @Override public synchronized void start() { if(running.get()) { return; } keepAlive.set(true); thread = new Thread(this, "Market data notifier thread for " + getProviderName()); //$NON-NLS-1$ thread.start(); } /* (non-Javadoc) * @see org.springframework.context.Lifecycle#stop() */ @Override public synchronized void stop() { if(!running.get()) { return; } keepAlive.set(false); if(thread != null) { thread.interrupt(); try { thread.join(); } catch (InterruptedException ignored) {} thread = null; } } /* (non-Javadoc) * @see org.springframework.context.Lifecycle#isRunning() */ @Override public boolean isRunning() { return running.get(); } /** * keeps the event notifier running */ private final AtomicBoolean keepAlive = new AtomicBoolean(false); /** * indicates if the event notifier is running */ private final AtomicBoolean running = new AtomicBoolean(false); /** * notifier thread */ private volatile Thread thread; } /** * Represents an event notification to be published. * * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @version $Id: AbstractMarketDataProvider.java 16912 2014-05-16 23:35:10Z colin $ * @since 2.4.0 */ @ClassVersion("$Id: AbstractMarketDataProvider.java 16912 2014-05-16 23:35:10Z colin $") private static class EventNotification { /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.SHORT_PREFIX_STYLE).append("instrument",instrument).append("content",content).append(" [") //$NON-NLS-1$ //$NON-NLS-2$ .append(Arrays.toString(events)).append(" ]").toString(); //$NON-NLS-1$ } /** * Create a new EventNotification instance. * * @param inContent a <code>Content</code> value * @param inInstrument an <code>Instrument</code> value * @param inEvents an <code>Event[]</code> value */ private EventNotification(Content inContent, Instrument inInstrument, Event... inEvents) { events = inEvents; content = inContent; instrument = inInstrument; } /** * content value */ private final Content content; /** * instrument value */ private final Instrument instrument; /** * events to notify */ private final Event[] events; } /** * feed status value */ private volatile ProviderStatus status = ProviderStatus.UNKNOWN; /** * indicates if the provider is running */ private final AtomicBoolean running = new AtomicBoolean(false); /** * provider registry value with which to register/unregister or <code>null</code> */ private volatile MarketDataProviderRegistry providerRegistry; /** * total number of requests submitted */ private volatile int totalRequests; /** * total number of events created */ private volatile int totalEvents; /** * notification collection that contains events to publish */ private final BlockingDeque<EventNotification> notifications = new LinkedBlockingDeque<EventNotification>(); /** * processes events to be published and publishes them */ private volatile EventNotifier notifier; /** * used to protect the market data collections */ private final ReadWriteLock marketdataLock = new ReentrantReadWriteLock(); /** * maps symbols or symbol fragments to the instrument value from the viewpoint of the market data provider */ @GuardedBy("marketdataLock") private final Map<String,Instrument> instrumentsBySymbol = new HashMap<String,Instrument>(); /** * tracks market data requests by the instrument in which they are interested */ @GuardedBy("marketdataLock") private final Multimap<Instrument,MarketDataRequestToken> requestsByInstrument = HashMultimap.create(); /** * tracks market data requests by the market data request atom created */ @GuardedBy("marketdataLock") private final Multimap<MarketDataRequestAtom,MarketDataRequestToken> requestsByAtom = HashMultimap.create(); /** * tracks market data requests by the symbol the request contained */ @GuardedBy("marketdataLock") private final Multimap<String,MarketDataRequestToken> requestsBySymbol = HashMultimap.create(); /** * tracks cached market data by the instrument */ @GuardedBy("marketdataLock") private final Map<Instrument,MarketdataCacheElement> cachedMarketdata = new HashMap<Instrument,MarketdataCacheElement>(); /** * maps the capabilities needed to honor a request of a particular content type */ private static final Map<Content,Capability> necessaryCapabilities; /** * provides one-time initialization of static components */ static { Map<Content,Capability> capabilities = new HashMap<Content,Capability>(); capabilities.put(Content.DIVIDEND,Capability.DIVIDEND); capabilities.put(Content.LATEST_TICK,Capability.LATEST_TICK); capabilities.put(Content.LEVEL_2,Capability.LEVEL_2); capabilities.put(Content.MARKET_STAT,Capability.MARKET_STAT); capabilities.put(Content.OPEN_BOOK,Capability.OPEN_BOOK); capabilities.put(Content.TOP_OF_BOOK,Capability.TOP_OF_BOOK); capabilities.put(Content.TOTAL_VIEW,Capability.TOTAL_VIEW); capabilities.put(Content.AGGREGATED_DEPTH,Capability.AGGREGATED_DEPTH); capabilities.put(Content.UNAGGREGATED_DEPTH,Capability.UNAGGREGATED_DEPTH); capabilities.put(Content.IMBALANCE,Capability.IMBALANCE); necessaryCapabilities = Collections.unmodifiableMap(capabilities); } }