/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.engine.view.worker; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import org.threeten.bp.Duration; import org.threeten.bp.Instant; import com.google.common.collect.Lists; import com.opengamma.engine.ComputationTargetSpecification; import com.opengamma.engine.marketdata.MarketDataListener; import com.opengamma.engine.marketdata.MarketDataProvider; import com.opengamma.engine.marketdata.MarketDataSnapshot; import com.opengamma.engine.marketdata.availability.MarketDataAvailabilityFilter; import com.opengamma.engine.marketdata.availability.MarketDataAvailabilityProvider; import com.opengamma.engine.marketdata.availability.MarketDataNotSatisfiableException; import com.opengamma.engine.marketdata.availability.UnionMarketDataAvailability; import com.opengamma.engine.marketdata.resolver.MarketDataProviderResolver; import com.opengamma.engine.marketdata.spec.MarketDataSpecification; import com.opengamma.engine.value.ValueRequirement; import com.opengamma.engine.value.ValueSpecification; import com.opengamma.livedata.UserPrincipal; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.tuple.Pair; /** * A source of market data that aggregates data from multiple underlying {@link MarketDataProvider}s. Each request for market data is handled by one of the underlying providers. When a subscription is * made the underlying providers are checked in priority order until one of them is able to provide the data. * <p> * All notifications of market data updates and subscription changes are delivered to all listeners. Therefore instances of this class shouldn't be shared between multiple view processes. */ public class SnapshottingViewExecutionDataProvider extends ViewExecutionDataProvider { private final MarketDataAvailabilityProvider _availabilityProvider; private final CopyOnWriteArraySet<MarketDataListener> _listeners = new CopyOnWriteArraySet<MarketDataListener>(); /** * @param user The user requesting the data, not null * @param specs Specifications of the underlying providers in priority order, not empty * @param resolver For resolving market data specifications into providers, not null * @throws IllegalArgumentException If any of the data providers in {@code specs} can't be resolved */ public SnapshottingViewExecutionDataProvider(final UserPrincipal user, final List<MarketDataSpecification> specs, final MarketDataProviderResolver resolver) { super(user, specs, resolver); final MarketDataListener listener = new Listener(); if (getSpecifications().size() == 1) { final MarketDataProvider provider = getProviders().get(0); provider.addListener(listener); _availabilityProvider = provider.getAvailabilityProvider(getSpecifications().get(0)); } else { int index = 0; for (MarketDataProvider provider : getProviders()) { provider.addListener(new CompositeListener(index++, listener)); } _availabilityProvider = new CompositeAvailabilityProvider(getProviders(), getSpecifications()); } } /** * Adds a listener that will be notified of market data updates and subscription changes. * * @param listener The listener, not null */ public void addListener(final MarketDataListener listener) { ArgumentChecker.notNull(listener, "listener"); _listeners.add(listener); } /** * Removes a listener. * * @param listener The listener, not null */ public void removeListener(final MarketDataListener listener) { _listeners.remove(listener); } /** * Sets up subscriptions for market data * * @param specifications The market data items, not null */ public void subscribe(final Set<ValueSpecification> specifications) { ArgumentChecker.notNull(specifications, "specifications"); final List<Set<ValueSpecification>> specificationsByProvider = partitionSpecificationsByProvider(getProviders().size(), specifications); for (int i = 0; i < specificationsByProvider.size(); i++) { final Set<ValueSpecification> subscribe = specificationsByProvider.get(i); if (!subscribe.isEmpty()) { getProviders().get(i).subscribe(subscribe); } } } /** * Unsubscribes from market data. * * @param specifications The subscriptions that should be removed, not null */ public void unsubscribe(final Set<ValueSpecification> specifications) { ArgumentChecker.notNull(specifications, "requirements"); final List<Set<ValueSpecification>> specificationsByProvider = partitionSpecificationsByProvider(getProviders().size(), specifications); for (int i = 0; i < specificationsByProvider.size(); i++) { final Set<ValueSpecification> unsubscribe = specificationsByProvider.get(i); if (!unsubscribe.isEmpty()) { getProviders().get(i).unsubscribe(unsubscribe); } } } /** * @return An availability provider backed by the availability providers of the underlying market data providers */ public MarketDataAvailabilityProvider getAvailabilityProvider() { return _availabilityProvider; } /** * @return A snapshot of market data backed by snapshots from the underlying providers. */ public MarketDataSnapshot snapshot() { final int providers = getProviders().size(); if (providers == 1) { return getProviders().get(0).snapshot(getSpecifications().get(0)); } final List<MarketDataSnapshot> snapshots = Lists.newArrayListWithCapacity(providers); for (int i = 0; i < providers; i++) { final MarketDataSnapshot snapshot = getProviders().get(i).snapshot(getSpecifications().get(i)); snapshots.add(snapshot); } return new CompositeMarketDataSnapshot(snapshots, new ValueSpecificationProvider(providers)); } public Duration getRealTimeDuration(final Instant fromInstant, final Instant toInstant) { return Duration.between(fromInstant, toInstant); } /** * Distributes the updates to the listeners. */ private class Listener implements MarketDataListener { @Override public void subscriptionsSucceeded(Collection<ValueSpecification> valueSpecifications) { for (final MarketDataListener listener : _listeners) { listener.subscriptionsSucceeded(valueSpecifications); } } @Override public void subscriptionFailed(ValueSpecification valueSpecification, final String msg) { for (final MarketDataListener listener : _listeners) { listener.subscriptionFailed(valueSpecification, msg); } } @Override public void subscriptionStopped(ValueSpecification valueSpecification) { for (final MarketDataListener listener : _listeners) { listener.subscriptionStopped(valueSpecification); } } @Override public void valuesChanged(Collection<ValueSpecification> valueSpecifications) { for (final MarketDataListener listener : _listeners) { listener.valuesChanged(valueSpecifications); } } } /** * Listens for updates from the underlying providers and distributes them to the listeners. */ private static class CompositeListener implements MarketDataListener { private final int _providerId; private final MarketDataListener _underlying; public CompositeListener(final int providerId, final MarketDataListener underlying) { _providerId = providerId; _underlying = underlying; } private ValueSpecification convertSpecification(final ValueSpecification valueSpecification) { return convertUnderlyingSpecification(_providerId, valueSpecification); } private Collection<ValueSpecification> convertSpecifications(final Collection<ValueSpecification> valueSpecifications) { final Collection<ValueSpecification> result = new ArrayList<ValueSpecification>(valueSpecifications.size()); for (final ValueSpecification valueSpecification : valueSpecifications) { result.add(convertSpecification(valueSpecification)); } return result; } @Override public void subscriptionsSucceeded(Collection<ValueSpecification> valueSpecifications) { _underlying.subscriptionsSucceeded(convertSpecifications(valueSpecifications)); } @Override public void subscriptionFailed(ValueSpecification valueSpecification, final String msg) { _underlying.subscriptionFailed(convertSpecification(valueSpecification), msg); } @Override public void subscriptionStopped(ValueSpecification valueSpecification) { _underlying.subscriptionStopped(convertSpecification(valueSpecification)); } @Override public void valuesChanged(Collection<ValueSpecification> valueSpecifications) { _underlying.valuesChanged(convertSpecifications(valueSpecifications)); } } /** * {@link MarketDataAvailabilityProvider} that checks the underlying providers for availability. If the data is available from any underlying provider then it is available. If it isn't available but * is missing from any of the underlying providers then it is missing. Otherwise it is unavailable. */ private static final class CompositeAvailabilityProvider implements MarketDataAvailabilityProvider { private final List<MarketDataAvailabilityProvider> _providers; private final Serializable _cacheHint; public CompositeAvailabilityProvider(final List<MarketDataProvider> providers, final List<MarketDataSpecification> specs) { _providers = new ArrayList<MarketDataAvailabilityProvider>(providers.size()); final ArrayList<Serializable> cacheHints = new ArrayList<Serializable>(providers.size()); for (int i = 0; i < providers.size(); i++) { final MarketDataAvailabilityProvider availabilityProvider = providers.get(i).getAvailabilityProvider(specs.get(i)); _providers.add(availabilityProvider); cacheHints.add(availabilityProvider.getAvailabilityHintKey()); } _cacheHint = cacheHints; } /** * @param desiredValue the market data requirement, not null * @return The satisfaction of the requirement from the underlying providers. */ @Override public ValueSpecification getAvailability(final ComputationTargetSpecification targetSpec, final Object target, final ValueRequirement desiredValue) { MarketDataNotSatisfiableException missing = null; for (int i = 0; i < _providers.size(); i++) { final MarketDataAvailabilityProvider provider = _providers.get(i); try { final ValueSpecification underlying = provider.getAvailability(targetSpec, target, desiredValue); if (underlying != null) { return convertUnderlyingSpecification(i, underlying); } } catch (final MarketDataNotSatisfiableException e) { missing = e; } } if (missing != null) { throw missing; } else { return null; } } @Override public MarketDataAvailabilityFilter getAvailabilityFilter() { final List<MarketDataAvailabilityFilter> union = new ArrayList<MarketDataAvailabilityFilter>(_providers.size()); for (MarketDataAvailabilityProvider provider : _providers) { union.add(provider.getAvailabilityFilter()); } return new UnionMarketDataAvailability.Filter(union); } @Override public Serializable getAvailabilityHintKey() { return _cacheHint; } } /* package */static class ValueSpecificationProvider { private final int _numProviders; /* package */ValueSpecificationProvider(final int numProviders) { _numProviders = numProviders; } public Pair<Integer, ValueSpecification> getUnderlyingAndSpecification(final ValueSpecification specification) { return SnapshottingViewExecutionDataProvider.getProviderSpecification(specification); } public List<Set<ValueSpecification>> getUnderlyingSpecifications(final Set<ValueSpecification> specifications) { return SnapshottingViewExecutionDataProvider.partitionSpecificationsByProvider(_numProviders, specifications); } public ValueSpecification convertUnderlyingSpecification(final int providerId, final ValueSpecification specification) { return SnapshottingViewExecutionDataProvider.convertUnderlyingSpecification(providerId, specification); } } }