package org.marketcetera.marketdata;
import static org.marketcetera.marketdata.Messages.BEAN_ATTRIBUTE_CHANGED;
import static org.marketcetera.marketdata.Messages.FEED_STATUS_CHANGED;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import javax.management.AttributeChangeNotification;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import org.marketcetera.core.CoreException;
import org.marketcetera.core.IFeedComponentListener;
import org.marketcetera.core.LockHelper;
import org.marketcetera.core.publisher.ISubscriber;
import org.marketcetera.event.Event;
import org.marketcetera.marketdata.IFeedComponent.FeedType;
import org.marketcetera.metrics.ThreadedMetric;
import org.marketcetera.module.DataEmitter;
import org.marketcetera.module.DataEmitterSupport;
import org.marketcetera.module.DataFlowID;
import org.marketcetera.module.DataRequest;
import org.marketcetera.module.IllegalRequestParameterValue;
import org.marketcetera.module.Module;
import org.marketcetera.module.ModuleException;
import org.marketcetera.module.ModuleURN;
import org.marketcetera.module.RequestID;
import org.marketcetera.module.UnsupportedRequestParameterType;
import org.marketcetera.util.log.SLF4JLoggerProxy;
import org.marketcetera.util.misc.ClassVersion;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Maps;
/* $License$ */
/**
* Base class for market data provider strategy agent modules.
*
* <p>Market data providers wishing to expose a strategy agent emitter interface
* may extend this class.
* <p>
* Module Features
* <table>
* <tr><th>Capabilities</th><td>Data Emitter</td></tr>
* <tr><th>Stops data flows</th><td>No</td></tr>
* <tr><th>Start Operation</th><td>Starts the feed, logs into it.</td></tr>
* <tr><th>Stop Operation</th><td>Stops the data feed.</td></tr>
* <tr><th>Management Interface</th><td>{@link AbstractMarketDataModuleMXBean}</td></tr>
* <tr><th>MX Notification</th><td>{@link AttributeChangeNotification}
* whenever {@link #getFeedStatus()} changes. </td></tr>
* </table>
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: AbstractMarketDataModule.java 16834 2014-02-13 20:40:53Z colin $
* @since 1.0.0
*/
@ClassVersion("$Id: AbstractMarketDataModule.java 16834 2014-02-13 20:40:53Z colin $") //$NON-NLS-1$
public abstract class AbstractMarketDataModule<T extends MarketDataFeedToken,
C extends MarketDataFeedCredentials>
extends Module
implements DataEmitter, AbstractMarketDataModuleMXBean, NotificationEmitter
{
/**
* Gets the feed module with the given provider name.
*
* @param inProviderName a <code>String</code> value
* @return an <code>AbstractMarketDataModule<?,?></code> value or <code>null</code>
*/
public static AbstractMarketDataModule<?,?> getFeedForProviderName(String inProviderName)
{
return feedsByProviderName.get(inProviderName);
}
/**
* Gets the feed type value.
*
* @return a <code>FeedType</code> value
*/
public FeedType getFeedType()
{
return feed.getFeedType();
}
/* (non-Javadoc)
* @see org.marketcetera.marketdata.AbstractMarketDataModuleMXBean#getFeedStatus()
*/
@Override
public final String getFeedStatus()
{
if(feedStatus == null) {
return FeedStatus.OFFLINE.toString();
}
return feedStatus.toString();
}
/* (non-Javadoc)
* @see org.marketcetera.marketdata.AbstractMarketDataModuleMXBean#reconnect()
*/
@Override
public void reconnect()
{
try {
feed.logout();
feed.login(getCredentials());
} catch (Exception e) {
SLF4JLoggerProxy.error(this,
e);
throw new IllegalArgumentException(e);
}
if(feed instanceof AbstractMarketDataFeed<?,?,?,?,?,?>) {
((AbstractMarketDataFeed<?,?,?,?,?,?>)feed).doReconnectToFeed();
} else {
throw new UnsupportedOperationException();
}
}
/* (non-Javadoc)
* @see org.marketcetera.module.DataEmitter#cancel(org.marketcetera.module.RequestID)
*/
@Override
public final void cancel(DataFlowID inFlowID,
final RequestID inRequestID)
{
requestLock.executeWrite(new Runnable() {
@Override
public void run()
{
T token = requests.inverse().remove(inRequestID);
if(token != null) {
token.cancel();
}
}
});
}
/* (non-Javadoc)
* @see org.marketcetera.module.DataEmitter#requestData(org.marketcetera.module.DataRequest, org.marketcetera.module.DataEmitterSupport)
*/
@Override
public final void requestData(DataRequest inRequest,
final DataEmitterSupport inSupport)
throws UnsupportedRequestParameterType, IllegalRequestParameterValue
{
Object requestPayload = inRequest.getData();
if(requestPayload == null) {
throw new IllegalRequestParameterValue(instanceURN,
null);
}
MarketDataRequest request = null;
if(requestPayload instanceof String) {
try {
request = MarketDataRequestBuilder.newRequestFromString((String)requestPayload);
} catch (Exception e) {
throw new IllegalRequestParameterValue(instanceURN,
requestPayload,
e);
}
} else if (requestPayload instanceof MarketDataRequest) {
request = (MarketDataRequest)requestPayload;
} else {
throw new UnsupportedRequestParameterType(instanceURN,
requestPayload);
}
try {
ISubscriber subscriber = new ISubscriber() {
@Override
public boolean isInteresting(Object inData)
{
return inData instanceof Event;
}
@Override
public void publishTo(final Object inEvent)
{
if(inEvent instanceof Event) {
requestLock.executeRead(new Runnable() {
@Override
public void run()
{
Event event = (Event)inEvent;
Object token = event.getSource();
RequestID requestID = requests.get(token);
event.setSource(requestID);
}
});
}
ThreadedMetric.event("mdata-OUT"); //$NON-NLS-1$
inSupport.send(inEvent);
}
};
MarketDataFeedTokenSpec spec = MarketDataFeedTokenSpec.generateTokenSpec(request,
subscriber);
final T token = feed.execute(spec);
requestLock.executeWrite(new Runnable() {
@Override
public void run()
{
requests.put(token,
inSupport.getRequestID());
}
});
} catch (Exception e) {
throw new IllegalRequestParameterValue(instanceURN,
requestPayload,
e);
}
}
/* (non-Javadoc)
* @see javax.management.NotificationEmitter#removeNotificationListener(javax.management.NotificationListener, javax.management.NotificationFilter, java.lang.Object)
*/
@Override
public final void removeNotificationListener(NotificationListener inListener,
NotificationFilter inFilter,
Object inHandback)
throws ListenerNotFoundException
{
mNotificationDelegate.removeNotificationListener(inListener,
inFilter,
inHandback);
}
/* (non-Javadoc)
* @see javax.management.NotificationBroadcaster#addNotificationListener(javax.management.NotificationListener, javax.management.NotificationFilter, java.lang.Object)
*/
@Override
public final void addNotificationListener(NotificationListener inListener,
NotificationFilter inFilter,
Object inHandback)
throws IllegalArgumentException
{
mNotificationDelegate.addNotificationListener(inListener,
inFilter,
inHandback);
}
/* (non-Javadoc)
* @see javax.management.NotificationBroadcaster#getNotificationInfo()
*/
@Override
public final MBeanNotificationInfo[] getNotificationInfo()
{
return mNotificationDelegate.getNotificationInfo();
}
/* (non-Javadoc)
* @see javax.management.NotificationBroadcaster#removeNotificationListener(javax.management.NotificationListener)
*/
@Override
public final void removeNotificationListener(NotificationListener inListener)
throws ListenerNotFoundException
{
mNotificationDelegate.removeNotificationListener(inListener);
}
/* (non-Javadoc)
* @see org.marketcetera.marketdata.AbstractMarketDataModuleMXBean#getCapabilities()
*/
@Override
public Set<Capability> getCapabilities()
{
Set<Capability> capabilities;
if(feed == null ||
(capabilities = feed.getCapabilities()) == null) {
return unknownCapabilities;
}
return capabilities;
}
/* (non-Javadoc)
* @see org.marketcetera.marketdata.AbstractMarketDataModuleMXBean#getAssetClasses()
*/
@Override
public Set<AssetClass> getAssetClasses()
{
Set<AssetClass> assetClasses;
if(feed == null ||
(assetClasses = feed.getSupportedAssetClasses()) == null) {
return unknownAssetClasses;
}
return assetClasses;
}
/**
* Create a new AbstractMarketDataModule instance.
*
* @param inInstanceURN a <code>ModuleURN</code> value containing the URN of the module
* @param inFeed an <code>IMarketDataFeed<T,C></code> value containing the instance of the market data feed that this module wraps
*/
protected AbstractMarketDataModule(ModuleURN inInstanceURN,
MarketDataFeed<T,C> inFeed)
{
super(inInstanceURN,
false);
instanceURN = inInstanceURN;
feed = inFeed;
feedStatus = feed.getFeedStatus();
MBeanNotificationInfo notifyInfo = new MBeanNotificationInfo(new String[] { AttributeChangeNotification.ATTRIBUTE_CHANGE },
AttributeChangeNotification.class.getName(),
BEAN_ATTRIBUTE_CHANGED.getText());
mNotificationDelegate = new NotificationBroadcasterSupport(notifyInfo);
feed.addFeedComponentListener(new IFeedComponentListener() {
@Override
public void feedComponentChanged(IFeedComponent inComponent)
{
setFeedStatus(inComponent.getFeedStatus());
}
});
feedsByProviderName.put(inInstanceURN.providerName(),
this);
}
/* (non-Javadoc)
* @see org.marketcetera.module.Module#preStart()
*/
@Override
protected void preStart()
throws ModuleException
{
SLF4JLoggerProxy.debug(this,
"starting {}", //$NON-NLS-1$
this);
feed.start();
try {
feed.login(getCredentials());
} catch (Exception e) {
throw new ModuleException(e);
}
}
/* (non-Javadoc)
* @see org.marketcetera.module.Module#preStop()
*/
@Override
protected void preStop()
throws ModuleException
{
feed.stop();
}
/**
* Returns a credentials instance relevant to this feed.
*
* @return a <code>C</code> value
* @throws CoreException if the credentials object cannot be created
*/
protected abstract C getCredentials()
throws CoreException;
/**
* Sets the feed status as tracked by the module.
*
* @param inNewFeedStatus a <code>FeedStatus</code> value
*/
private void setFeedStatus(FeedStatus inNewFeedStatus)
{
String newStatusString = inNewFeedStatus.toString();
String oldStatusString = feedStatus.toString();
feedStatus = inNewFeedStatus;
mNotificationDelegate.sendNotification(new AttributeChangeNotification(this,
mSequence.getAndIncrement(),
System.currentTimeMillis(),
FEED_STATUS_CHANGED.getText(),
"FeedStatus", //$NON-NLS-1$
"String", //$NON-NLS-1$
oldStatusString,
newStatusString));
}
/**
* tracks feeds by provider name as the feeds are instantiated (not started) - may not be active
*/
private final static Map<String,AbstractMarketDataModule<?,?>> feedsByProviderName = Maps.newHashMap();
/**
* this is the unique instance URN of the module
*/
private final ModuleURN instanceURN;
/**
* the actual feed object that handles market data requests
*/
private final MarketDataFeed<T,C> feed;
/**
* tracks the tokens of active data requests
*/
private final BiMap<T,RequestID> requests = HashBiMap.create();
/**
* lock used for {@link #requests} access
*/
private final LockHelper requestLock = new LockHelper();
/**
* provides JMX notification services
*/
private final NotificationBroadcasterSupport mNotificationDelegate;
/**
* counter providing increasing sequence of values unique to this JVM instance
*/
private final AtomicLong mSequence = new AtomicLong();
/**
* the feed status of the underlying feed object
*/
private FeedStatus feedStatus;
/**
* used to indicate unknown capabilities of a provider
*/
private static final Set<Capability> unknownCapabilities = EnumSet.of(Capability.UNKNOWN);
/**
* used to indicate unknown supported asset classes of a provider
*/
private static final Set<AssetClass> unknownAssetClasses = EnumSet.noneOf(AssetClass.class);
}