/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.livedata.server;
import java.util.Collection;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import org.fudgemsg.FudgeMsg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.livedata.LiveDataSpecification;
import com.opengamma.livedata.server.distribution.MarketDataDistributor;
import com.opengamma.livedata.server.distribution.MarketDataSenderFactory;
import com.opengamma.util.ArgumentChecker;
/**
* A record of a market data subscription currently active on a server.
*/
public class Subscription {
/** Logger. */
private static final Logger s_logger = LoggerFactory.getLogger(Subscription.class);
/**
* The unique ID that was subscribed to, specific to the market data provider, such as Bloomberg/Reuters.
*/
private final String _securityUniqueId;
/**
* Controls how the data from this subscription will be sent.
*/
private final MarketDataSenderFactory _marketDataSenderFactory;
/**
* A lock to enforce that live data is handled in a serialized and thus safe & ordered fashion.
*/
private final ReentrantLock _liveDataSerializationLock = new ReentrantLock();
/**
* The data from this subscription can be distributed to clients in multiple formats,
* therefore we need multiple market data distributors.
* NOTE: this is a concurrent map for speedy reads
* <p>
*/
private final ConcurrentHashMap<DistributionSpecification, MarketDataDistributor> _distributors = new ConcurrentHashMap<DistributionSpecification, MarketDataDistributor>();
/**
* The handle to the underlying subscription, specific to the market data provider, such as Bloomberg/Reuters.
* May be null if the subscription is not currently active.
*/
private volatile Object _handle;
/**
* History of ticks received from the underlying market data API, in its native format.
*/
private final FieldHistoryStore _history = new FieldHistoryStore();
/**
* The creation instant.
*/
private final Date _creationTime;
/**
* The provider of last known value stores.
*/
private final LastKnownValueStoreProvider _lkvStoreProvider;
/**
* Creates an instance.
*
* @param securityUniqueId the security unique ID, specific to the market data provider, not null
* @param marketDataSenderFactory the factory that will create market data distributors for this subscription, not null
* @param lkvStoreProvider the factory for last known value stores, not null
*/
public Subscription(String securityUniqueId, MarketDataSenderFactory marketDataSenderFactory, LastKnownValueStoreProvider lkvStoreProvider) {
ArgumentChecker.notNull(securityUniqueId, "securityUniqueId");
ArgumentChecker.notNull(marketDataSenderFactory, "marketDataSenderFactory");
ArgumentChecker.notNull(lkvStoreProvider, "lkvStoreProvider");
_securityUniqueId = securityUniqueId;
_marketDataSenderFactory = marketDataSenderFactory;
_creationTime = new Date();
_lkvStoreProvider = lkvStoreProvider;
}
//-------------------------------------------------------------------------
/**
* Gets the opaque handle to the underlying subscription, specific to the market data provider.
*
* @return the opaque handle, null if the subscription is not currently active
*/
public Object getHandle() {
return _handle;
}
/**
* Sets the opaque handle to the underlying subscription, specific to the market data provider.
*
* @param handle the opaque handle, null if the subscription is not currently active
*/
public void setHandle(Object handle) {
_handle = handle;
}
/**
* Gets the creation instant.
*
* @return the creation instant, not null
*/
public Date getCreationTime() {
return _creationTime;
}
/**
* Gets the unique ID that was subscribed to, specific to the market data provider.
*
* @return the market data provider unique ID, not null
*/
public String getSecurityUniqueId() {
return _securityUniqueId;
}
/**
* Gets the factory used to create distributors.
*
* @return the factory, not null
*/
public MarketDataSenderFactory getMarketDataSenderFactory() {
return _marketDataSenderFactory;
}
//-------------------------------------------------------------------------
/**
* Gets the set of distribution specifications.
*
* @return a modifiable copy of the specifications, not null
*/
public Set<DistributionSpecification> getDistributionSpecifications() {
return _distributors.keySet();
}
/**
* Gets the set of distributors.
*
* @return a modifiable copy of the distributors, not null
*/
public Collection<MarketDataDistributor> getDistributors() {
return _distributors.values();
}
/**
* Gets a specific distributor by distribution specification.
*
* @param distributionSpec the specification to find
* @return the distributor, null if not found
*/
public MarketDataDistributor getMarketDataDistributor(DistributionSpecification distributionSpec) {
return _distributors.get(distributionSpec);
}
/**
* Gets a specific distributor by specification.
*
* @param fullyQualifiedSpec the specification to find
* @return the distributor, null if not found
*/
public MarketDataDistributor getMarketDataDistributor(LiveDataSpecification fullyQualifiedSpec) {
for (MarketDataDistributor distributor : getDistributors()) {
if (distributor.getDistributionSpec().getFullyQualifiedLiveDataSpecification().equals(fullyQualifiedSpec)) {
return distributor;
}
}
return null;
}
/**
* Gets the provider of last known value stores.
*
* @return the provider of last known value stores, not null
*/
public LastKnownValueStoreProvider getLkvStoreProvider() {
return _lkvStoreProvider;
}
//-------------------------------------------------------------------------
/**
* Tells this subscription to start distributing market data in the given format.
* Only creates a new distribution if it doesn't already exist.
*
* @param spec the format to use
* @param persistent whether the distributor should be persistent (survive a server restart)
* @return the created/modified {@code MarketDataDistributor}
*/
/*package*/ MarketDataDistributor createDistributor(DistributionSpecification spec, boolean persistent) {
MarketDataDistributor distributor = getMarketDataDistributor(spec);
if (distributor == null) {
distributor = new MarketDataDistributor(spec, this, getMarketDataSenderFactory(), persistent, getLkvStoreProvider());
MarketDataDistributor previous = _distributors.putIfAbsent(spec, distributor);
if (previous == null) {
s_logger.info("Added {} to {}", distributor, this);
} else {
s_logger.debug("Lost race to create distributor {} to {}", previous, this);
distributor = previous;
}
}
// Might be necessary to make the distributor persistent. We
// never turn it back from persistent to non-persistent, however.
if (!distributor.isPersistent() && persistent) {
distributor.setPersistent(persistent);
s_logger.info("Made {} persistent", distributor);
}
return distributor;
}
/*package*/ void removeDistributor(MarketDataDistributor distributor) {
removeDistributor(distributor.getDistributionSpec());
}
/*package*/ void removeDistributor(DistributionSpecification spec) {
MarketDataDistributor removed = _distributors.remove(spec);
if (removed != null) {
s_logger.info("Removed {} from {}", removed, this);
} else {
s_logger.info("Removed distribution spec {} from {} (no-op)", spec, this);
}
}
/*package*/ void removeAllDistributors() {
s_logger.info("Removed {} from {}", _distributors, this);
_distributors.clear();
}
/*package*/ void initialSnapshotReceived(FudgeMsg liveDataFields) {
_liveDataSerializationLock.lock();
try {
_history.liveDataReceived(liveDataFields);
for (MarketDataDistributor distributor : getDistributors()) {
distributor.updateFieldHistory(liveDataFields);
}
} finally {
_liveDataSerializationLock.unlock();
}
}
/*package*/ void liveDataReceived(FudgeMsg liveDataFields) {
_liveDataSerializationLock.lock();
try {
_history.liveDataReceived(liveDataFields);
for (MarketDataDistributor distributor : getDistributors()) {
distributor.distributeLiveData(liveDataFields);
}
} finally {
_liveDataSerializationLock.unlock();
}
}
//-------------------------------------------------------------------------
/**
* Gets the history.
*
* @return a modifiable copy of the history, not null
*/
public FieldHistoryStore getLiveDataHistory() {
_liveDataSerializationLock.lock();
try {
return new FieldHistoryStore(_history);
} finally {
_liveDataSerializationLock.unlock();
}
}
/**
* Checks if the subscription is active.
*
* @return true if active
*/
public boolean isActive() {
return getHandle() != null;
}
//-------------------------------------------------------------------------
@Override
public String toString() {
return "Subscription[" + _securityUniqueId + "]";
}
}