/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.engine.marketdata; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import com.google.common.collect.Sets; import com.opengamma.engine.ComputationTargetSpecification; 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.spec.CombinedMarketDataSpecification; import com.opengamma.engine.marketdata.spec.MarketDataSpecification; import com.opengamma.engine.value.ValueProperties; import com.opengamma.engine.value.ValuePropertyNames; import com.opengamma.engine.value.ValueRequirement; import com.opengamma.engine.value.ValueSpecification; import com.opengamma.livedata.UserPrincipal; import com.opengamma.util.tuple.Pair; import com.opengamma.util.tuple.Pairs; /** * Implementation of {@link MarketDataProvider} which sources its data from one of two {@link MarketDataProvider}s, choosing based on the availability of data. */ public class CombinedMarketDataProvider extends AbstractMarketDataProvider { private static final String PREFERRED_PROVIDER = "Preferred"; private static final String FALLBACK_PROVIDER = "Fallback"; private final MarketDataProvider _preferred; private final MarketDataProvider _fallBack; private final class Listener implements MarketDataListener { private final String _provider; public Listener(final String provider) { _provider = provider; } private ValueSpecification createValueSpecification(final ValueSpecification specification) { return CombinedMarketDataProvider.this.createValueSpecification(specification, _provider); } private Collection<ValueSpecification> createValueSpecifications(final Collection<ValueSpecification> specifications) { return CombinedMarketDataProvider.this.createValueSpecifications(specifications, _provider); } @Override public void subscriptionsSucceeded(final Collection<ValueSpecification> specifications) { CombinedMarketDataProvider.this.subscriptionsSucceeded(createValueSpecifications(specifications)); } @Override public void subscriptionFailed(final ValueSpecification specification, final String msg) { CombinedMarketDataProvider.this.subscriptionFailed(createValueSpecification(specification), msg); } @Override public void subscriptionStopped(final ValueSpecification specification) { CombinedMarketDataProvider.this.subscriptionStopped(createValueSpecification(specification)); } @Override public void valuesChanged(final Collection<ValueSpecification> specifications) { CombinedMarketDataProvider.this.valuesChanged(createValueSpecifications(specifications)); } }; private final Listener _preferredListener = new Listener(PREFERRED_PROVIDER); private final Listener _fallbackListener = new Listener(FALLBACK_PROVIDER); private boolean _listenerAttached; public CombinedMarketDataProvider(final MarketDataProvider preferred, final MarketDataProvider fallBack) { _preferred = preferred; _fallBack = fallBack; } @Override public void addListener(final MarketDataListener listener) { super.addListener(listener); checkListenerAttach(); } @Override public void removeListener(final MarketDataListener listener) { super.removeListener(listener); checkListenerAttach(); } private void checkListenerAttach() { //TODO: dedupe with CombinedMarketDataProvider synchronized (_preferredListener) { final boolean anyListeners = getListeners().size() > 0; if (anyListeners && !_listenerAttached) { _preferred.addListener(_preferredListener); _fallBack.addListener(_fallbackListener); _listenerAttached = true; } else if (!anyListeners && _listenerAttached) { _preferred.removeListener(_preferredListener); _fallBack.removeListener(_fallbackListener); _listenerAttached = false; } } } private ValueSpecification createValueSpecification(final ValueSpecification underlying, final String provider) { final ValueProperties.Builder properties = underlying.getProperties().copy(); final String dataProvider = underlying.getProperty(ValuePropertyNames.DATA_PROVIDER); if (dataProvider != null) { properties.withoutAny(ValuePropertyNames.DATA_PROVIDER).with(ValuePropertyNames.DATA_PROVIDER, dataProvider + "/" + provider); } else { properties.with(ValuePropertyNames.DATA_PROVIDER, provider); } return new ValueSpecification(underlying.getValueName(), underlying.getTargetSpecification(), properties.get()); } private Collection<ValueSpecification> createValueSpecifications(final Collection<ValueSpecification> underlyings, final String provider) { final Collection<ValueSpecification> result = new ArrayList<ValueSpecification>(underlyings.size()); for (ValueSpecification underlying : underlyings) { result.add(createValueSpecification(underlying, provider)); } return result; } /** * Creates an availability provider that combines results from the two providers. The returned specification is either: * <ul> * <li>The value specification from the preferred provider * <li>The value specification from the non-preferred provider * <li>Null if either provider returned null * <li>{@link MarketDataNotSatisfiableException} being thrown if both providers do so * </ul> * * @param marketDataSpec the market data specification, not null * @return the provider, not null */ @Override public MarketDataAvailabilityProvider getAvailabilityProvider(final MarketDataSpecification marketDataSpec) { return new MarketDataAvailabilityProvider() { private final MarketDataAvailabilityProvider _preferredProvider = _preferred.getAvailabilityProvider(marketDataSpec); private final MarketDataAvailabilityProvider _fallbackProvider = _fallBack.getAvailabilityProvider(marketDataSpec); @Override public ValueSpecification getAvailability(final ComputationTargetSpecification targetSpec, final Object target, final ValueRequirement desiredValue) { MarketDataNotSatisfiableException preferredMissing = null; try { final ValueSpecification preferred = _preferredProvider.getAvailability(targetSpec, target, desiredValue); if (preferred != null) { // preferred is available return createValueSpecification(preferred, PREFERRED_PROVIDER); } } catch (final MarketDataNotSatisfiableException e) { preferredMissing = e; } try { final ValueSpecification fallback = _fallbackProvider.getAvailability(targetSpec, target, desiredValue); if (fallback != null) { // fallback is available return createValueSpecification(fallback, FALLBACK_PROVIDER); } } catch (final MarketDataNotSatisfiableException e) { if (preferredMissing != null) { // both are not available - use preferred throw preferredMissing; } else { // fallback is not available throw e; } } // preferred is either not available or missing if (preferredMissing != null) { throw preferredMissing; } else { return null; } } @Override public MarketDataAvailabilityFilter getAvailabilityFilter() { return new UnionMarketDataAvailability.Filter(Arrays.asList(_preferredProvider.getAvailabilityFilter(), _fallbackProvider.getAvailabilityFilter())); } @Override public Serializable getAvailabilityHintKey() { final ArrayList<Serializable> key = new ArrayList<Serializable>(2); key.add(_preferredProvider.getAvailabilityHintKey()); key.add(_fallbackProvider.getAvailabilityHintKey()); return key; } }; } @Override public MarketDataPermissionProvider getPermissionProvider() { return new MarketDataPermissionProvider() { @Override public Set<ValueSpecification> checkMarketDataPermissions(final UserPrincipal user, final Set<ValueSpecification> specifications) { final Map<MarketDataProvider, Set<ValueSpecification>> specsByProvider = getProviders(specifications); final Set<ValueSpecification> failures = Sets.newHashSet(); for (final Entry<MarketDataProvider, Set<ValueSpecification>> entry : specsByProvider.entrySet()) { final MarketDataPermissionProvider permissionProvider = entry.getKey().getPermissionProvider(); final Set<ValueSpecification> failed = permissionProvider.checkMarketDataPermissions(user, entry.getValue()); failures.addAll(failed); } return failures; } }; } @Override public void subscribe(final ValueSpecification valueSpecification) { subscribe(Collections.singleton(valueSpecification)); } @Override public void subscribe(final Set<ValueSpecification> valueSpecification) { final Map<MarketDataProvider, Set<ValueSpecification>> specificationsByProvider = getProviders(valueSpecification); for (final Entry<MarketDataProvider, Set<ValueSpecification>> entry : specificationsByProvider.entrySet()) { entry.getKey().subscribe(entry.getValue()); } } @Override public void unsubscribe(final ValueSpecification valueSpecification) { unsubscribe(Collections.singleton(valueSpecification)); } @Override public void unsubscribe(final Set<ValueSpecification> valueSpecifications) { final Map<MarketDataProvider, Set<ValueSpecification>> specificationsByProvider = getProviders(valueSpecifications); for (final Entry<MarketDataProvider, Set<ValueSpecification>> entry : specificationsByProvider.entrySet()) { entry.getKey().unsubscribe(entry.getValue()); } } @Override public MarketDataSnapshot snapshot(final MarketDataSpecification marketDataSpec) { final CombinedMarketDataSpecification combinedSpec = (CombinedMarketDataSpecification) marketDataSpec; final Map<MarketDataProvider, MarketDataSnapshot> snapByProvider = new HashMap<MarketDataProvider, MarketDataSnapshot>(); snapByProvider.put(_preferred, _preferred.snapshot(combinedSpec.getPreferredSpecification())); snapByProvider.put(_fallBack, _fallBack.snapshot(combinedSpec.getFallbackSpecification())); final MarketDataSnapshot preferredSnap = snapByProvider.get(_preferred); return new CombinedMarketDataSnapshot(preferredSnap, snapByProvider, this); } @Override public boolean isCompatible(final MarketDataSpecification marketDataSpec) { if (!(marketDataSpec instanceof CombinedMarketDataSpecification)) { return false; } final CombinedMarketDataSpecification combinedMarketDataSpec = (CombinedMarketDataSpecification) marketDataSpec; return _preferred.isCompatible(combinedMarketDataSpec.getPreferredSpecification()) && _fallBack.isCompatible(combinedMarketDataSpec.getFallbackSpecification()); } private MarketDataProvider getDataProvider(final ValueSpecification specification, final ValueProperties.Builder underlyingProperties) { String dataProvider = specification.getProperty(ValuePropertyNames.DATA_PROVIDER); if (dataProvider == null) { throw new IllegalArgumentException("Don't know how to provide " + specification); } underlyingProperties.withoutAny(ValuePropertyNames.DATA_PROVIDER); final int slash = dataProvider.lastIndexOf('/'); if (slash > 0) { underlyingProperties.with(ValuePropertyNames.DATA_PROVIDER, dataProvider.substring(0, slash)); dataProvider = dataProvider.substring(slash + 1); } if (PREFERRED_PROVIDER.equals(dataProvider)) { return _preferred; } else if (FALLBACK_PROVIDER.equals(dataProvider)) { return _fallBack; } else { throw new IllegalArgumentException("Don't know how to provide " + specification); } } public Map<MarketDataProvider, Set<ValueSpecification>> getProviders(final Collection<ValueSpecification> specifications) { final Map<MarketDataProvider, Set<ValueSpecification>> result = new HashMap<MarketDataProvider, Set<ValueSpecification>>(); for (final ValueSpecification specification : specifications) { final ValueProperties.Builder underlyingProperties = specification.getProperties().copy(); final MarketDataProvider provider = getDataProvider(specification, underlyingProperties); Set<ValueSpecification> set = result.get(provider); if (set == null) { set = new HashSet<ValueSpecification>(); result.put(provider, set); } set.add(new ValueSpecification(specification.getValueName(), specification.getTargetSpecification(), underlyingProperties.get())); } return result; } /** * Returns the specifications per provider. * * @param specifications the specification to convert * @return the map of provider to specification. The specifications returned a */ protected Map<MarketDataProvider, Map<ValueSpecification, ValueSpecification>> getProvidersAsMap(final Collection<ValueSpecification> specifications) { final Map<MarketDataProvider, Map<ValueSpecification, ValueSpecification>> result = new HashMap<MarketDataProvider, Map<ValueSpecification, ValueSpecification>>(); for (final ValueSpecification specification : specifications) { final ValueProperties.Builder underlyingProperties = specification.getProperties().copy(); final MarketDataProvider provider = getDataProvider(specification, underlyingProperties); Map<ValueSpecification, ValueSpecification> map = result.get(provider); if (map == null) { map = new HashMap<ValueSpecification, ValueSpecification>(); result.put(provider, map); } map.put(new ValueSpecification(specification.getValueName(), specification.getTargetSpecification(), underlyingProperties.get()), specification); } return result; } protected Pair<MarketDataProvider, ValueSpecification> getProvider(final ValueSpecification specification) { final ValueProperties.Builder underlyingProperties = specification.getProperties().copy(); final MarketDataProvider provider = getDataProvider(specification, underlyingProperties); return Pairs.of(provider, new ValueSpecification(specification.getValueName(), specification.getTargetSpecification(), underlyingProperties.get())); } }