package org.marketcetera.photon.internal.marketdata;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.core.runtime.ListenerList;
import org.marketcetera.core.notifications.ServerStatusListener;
import org.marketcetera.marketdata.Capability;
import org.marketcetera.marketdata.FeedStatus;
import org.marketcetera.marketdata.core.rpc.MarketDataRpcClientFactory;
import org.marketcetera.marketdata.core.webservice.ConnectionException;
import org.marketcetera.marketdata.core.webservice.CredentialsException;
import org.marketcetera.marketdata.core.webservice.MarketDataServiceClient;
import org.marketcetera.marketdata.core.webservice.UnknownHostException;
import org.marketcetera.marketdata.core.webservice.impl.MarketDataContextClassProvider;
import org.marketcetera.photon.core.ICredentials;
import org.marketcetera.photon.core.ICredentialsService;
import org.marketcetera.photon.core.ICredentialsService.IAuthenticationHelper;
import org.marketcetera.photon.marketdata.IFeedStatusChangedListener;
import org.marketcetera.photon.marketdata.IFeedStatusChangedListener.IFeedStatusEvent;
import org.marketcetera.photon.marketdata.IMarketData;
import org.marketcetera.photon.marketdata.IMarketDataManager;
import org.marketcetera.util.log.SLF4JLoggerProxy;
import org.marketcetera.util.misc.ClassVersion;
import com.google.inject.Inject;
/* $License$ */
/**
* Internal implementation of {@link IMarketDataManager}.
*
* @author <a href="mailto:will@marketcetera.com">Will Horn</a>
* @version $Id: MarketDataManager.java 16899 2014-05-11 16:03:04Z colin $
* @since 1.0.0
*/
@ClassVersion("$Id: MarketDataManager.java 16899 2014-05-11 16:03:04Z colin $")
public final class MarketDataManager
implements IMarketDataManager,IMarketDataClientProvider,ServerStatusListener
{
/**
* Create a new MarketDataManager instance.
*
* @param marketData
*/
@Inject
public MarketDataManager(final MarketData marketData)
{
mMarketData = marketData;
}
/**
* Gets a <code>MarketDataServiceClient</code>, creating one if necessary.
*
* @return a <code>MarketDataServiceClient</code> value
*/
public MarketDataServiceClient getMarketDataClient()
{
return marketDataClient;
}
@Override
public IMarketData getMarketData()
{
return mMarketData;
}
/* (non-Javadoc)
* @see org.marketcetera.photon.marketdata.IMarketDataManager#getAvailabilityCapability()
*/
@Override
public Set<Capability> getAvailabilityCapability()
{
MarketDataServiceClient client = getMarketDataClient();
if(client == null) {
return Collections.emptySet();
}
return client.getAvailableCapability();
}
/* (non-Javadoc)
* @see org.marketcetera.core.notifications.ServerStatusListener#receiveServerStatus(boolean)
*/
@Override
public void receiveServerStatus(boolean inStatus)
{
FeedStatusNotification update = new FeedStatusNotification(getFeedStatus(),
FeedStatus.UNKNOWN);
try {
if(inStatus) {
update.newFeedStatus = FeedStatus.AVAILABLE;
} else {
update.newFeedStatus = FeedStatus.ERROR;
if(mMarketData != null) {
mMarketData.reset();
}
if(marketDataClient != null) {
try {
marketDataClient.stop();
} catch (Exception ignored) {}
marketDataClient = null;
}
}
} finally {
notifyListeners(update);
}
}
/* (non-Javadoc)
* @see org.marketcetera.photon.marketdata.IMarketDataManager#setCredentialsService(org.marketcetera.photon.core.ICredentialsService)
*/
@Override
public void setCredentialsService(ICredentialsService inCredentialsService)
{
credentialsService = inCredentialsService;
}
/* (non-Javadoc)
* @see org.marketcetera.photon.marketdata.IMarketDataManager#close()
*/
@Override
public void close()
{
if(marketDataClient != null) {
try {
marketDataClient.stop();
} catch (Exception ignored) {}
marketDataClient = null;
}
}
@Override
public void reconnectFeed()
{
if(!reconnecting.compareAndSet(false,
true)) {
return;
}
try {
if(marketDataClient != null) {
marketDataClient.stop();
}
connect();
mMarketData.resubmit();
if(marketDataClient != null && marketDataClient.isRunning()) {
SLF4JLoggerProxy.info(org.marketcetera.core.Messages.USER_MSG_CATEGORY,
"Market Data Nexus connection established");
}
} catch (ConnectionException e) {
SLF4JLoggerProxy.error(org.marketcetera.core.Messages.USER_MSG_CATEGORY,
"Cannot connect to the Market Data Nexus at {}:{}",
hostname,
port);
SLF4JLoggerProxy.error(this,
e,
"Cannot connect to the Market Data Nexus at {}:{}",
e.getHostname(),
e.getPort());
} catch (UnknownHostException e) {
SLF4JLoggerProxy.error(org.marketcetera.core.Messages.USER_MSG_CATEGORY,
"Cannot connect to the Market Data Nexus at {}:{}",
hostname,
port);
SLF4JLoggerProxy.error(this,
e,
"Cannot connect to the Market Data Nexus at {}:{}",
e.getHostname(),
e.getPort());
} catch (CredentialsException e) {
SLF4JLoggerProxy.error(org.marketcetera.core.Messages.USER_MSG_CATEGORY,
"The Market Data Nexus rejected the login attempt as {}",
e.getUsername());
SLF4JLoggerProxy.error(this,
e,
"The Market Data Nexus rejected the login attempt as {}",
e.getUsername());
} catch (Exception e) {
SLF4JLoggerProxy.error(this,
e);
throw new RuntimeException(e);
} finally {
reconnecting.set(false);
}
}
@Override
public void addActiveFeedStatusChangedListener(
final IFeedStatusChangedListener listener) {
mActiveFeedListeners.add(listener);
}
@Override
public void removeActiveFeedStatusChangedListener(
final IFeedStatusChangedListener listener) {
mActiveFeedListeners.remove(listener);
}
/* (non-Javadoc)
* @see org.marketcetera.photon.marketdata.IMarketDataManager#setHostname(java.lang.String)
*/
@Override
public void setHostname(String inHostname)
{
hostname = inHostname;
}
/* (non-Javadoc)
* @see org.marketcetera.photon.marketdata.IMarketDataManager#setPort(int)
*/
@Override
public void setPort(int inPort)
{
port = inPort;
}
/**
* Indicates if the market data connection is ready.
*
* @return a <code>boolean</code> value
*/
public boolean isRunning()
{
if(marketDataClient == null || !marketDataClient.isRunning()) {
return false;
}
return true;
}
/* (non-Javadoc)
* @see org.marketcetera.photon.marketdata.IMarketDataManager#isReconnecting()
*/
@Override
public boolean isReconnecting()
{
return reconnecting.get();
}
/**
* Connects to the market data server, if necessary.
*/
private void connect()
{
if(marketDataClient == null || !marketDataClient.isRunning()) {
notifyListeners(new FeedStatusNotification(getFeedStatus(),
FeedStatus.UNKNOWN));
FeedStatusNotification update = new FeedStatusNotification(getFeedStatus(),
FeedStatus.UNKNOWN);
try {
boolean success = credentialsService.authenticateWithCredentials(new IAuthenticationHelper() {
@Override
public boolean authenticate(ICredentials inCredentials)
{
marketDataClient = new MarketDataRpcClientFactory().create(inCredentials.getUsername(),
inCredentials.getPassword(),
hostname,
port,
new MarketDataContextClassProvider());
marketDataClient.start();
return marketDataClient.isRunning();
}
});
update.newFeedStatus = success ? FeedStatus.AVAILABLE : FeedStatus.OFFLINE;
} catch (ConnectionException | CredentialsException | UnknownHostException e) {
update.newFeedStatus = FeedStatus.ERROR;
throw e;
} catch (RuntimeException e) {
update.newFeedStatus = FeedStatus.ERROR;
throw e;
} finally {
if(marketDataClient != null) {
marketDataClient.addServerStatusListener(MarketDataManager.this);
}
notifyListeners(update);
}
}
}
/**
* Notifies listeners of a feed status change event.
*
* @param inEvent an <code>IFeedStatusEvent</code> value
*/
private void notifyListeners(final IFeedStatusEvent inEvent)
{
Object[] listeners = mActiveFeedListeners.getListeners();
for(Object object : listeners) {
((IFeedStatusChangedListener)object).feedStatusChanged(inEvent);
}
}
@Override
public FeedStatus getFeedStatus()
{
if(marketDataClient == null) {
return FeedStatus.OFFLINE;
}
return marketDataClient.isRunning() ? FeedStatus.AVAILABLE : FeedStatus.ERROR;
}
/**
* Manages notifications for feed status subscribers.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: MarketDataManager.java 16899 2014-05-11 16:03:04Z colin $
* @since 2.4.0
*/
@ClassVersion("$Id: MarketDataManager.java 16899 2014-05-11 16:03:04Z colin $")
private class FeedStatusNotification
implements IFeedStatusEvent
{
/**
* Create a new FeedStatusNotification instance.
*
* @param inOldStatus a <code>FeedStatus</code> value
* @param inNewStatus a <code>FeedStatus</code> value
*/
private FeedStatusNotification(FeedStatus inOldStatus,
FeedStatus inNewStatus)
{
oldFeedStatus = inOldStatus;
newFeedStatus = inNewStatus;
}
/* (non-Javadoc)
* @see org.marketcetera.photon.marketdata.IFeedStatusChangedListener.IFeedStatusEvent#getSource()
*/
@Override
public Object getSource()
{
return this;
}
/* (non-Javadoc)
* @see org.marketcetera.photon.marketdata.IFeedStatusChangedListener.IFeedStatusEvent#getOldStatus()
*/
@Override
public FeedStatus getOldStatus()
{
return oldFeedStatus;
}
/* (non-Javadoc)
* @see org.marketcetera.photon.marketdata.IFeedStatusChangedListener.IFeedStatusEvent#getNewStatus()
*/
@Override
public FeedStatus getNewStatus()
{
return newFeedStatus;
}
/**
* old feed status value
*/
private FeedStatus oldFeedStatus = FeedStatus.UNKNOWN;
/**
* new feed status value
*/
private FeedStatus newFeedStatus = FeedStatus.UNKNOWN;
}
/**
* market data nexus hostname
*/
private String hostname;
/**
* market data nexus port
*/
private int port;
/**
* contains everybody who has expressed an interest in feed status
*/
private final ListenerList mActiveFeedListeners = new ListenerList();
/**
* indicates if the manager is current reconnecting
*/
private final AtomicBoolean reconnecting = new AtomicBoolean(false);
/**
* market data flow manager
*/
private final MarketData mMarketData;
/**
* connection to the market data service
*/
private MarketDataServiceClient marketDataClient;
/**
* provides access to the credentials used to connect to the server
*/
private ICredentialsService credentialsService;
}