/** * 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.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import org.fudgemsg.FudgeMsg; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Function; import com.google.common.collect.Sets; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.id.ExternalScheme; import com.opengamma.livedata.LiveDataSpecification; import com.opengamma.livedata.msg.LiveDataSubscriptionRequest; import com.opengamma.livedata.msg.LiveDataSubscriptionResponse; import com.opengamma.livedata.msg.LiveDataSubscriptionResponseMsg; import com.opengamma.livedata.server.distribution.MarketDataDistributor; import com.opengamma.util.NamedThreadPoolFactory; import com.opengamma.util.tuple.Pair; import com.opengamma.util.tuple.Pairs; import net.sf.ehcache.CacheManager; /** * A {@link StandardLiveDataServer} which delegates all the work to a set of {@link StandardLiveDataServer} */ public abstract class CombiningLiveDataServer extends StandardLiveDataServer { private static final Logger s_logger = LoggerFactory.getLogger(CombiningLiveDataServer.class); private static final ExecutorService s_subscriptionExecutor = NamedThreadPoolFactory.newCachedThreadPool("CombiningLiveDataServer", true); private final Set<StandardLiveDataServer> _underlyings; public CombiningLiveDataServer(CacheManager cacheManager, StandardLiveDataServer... otherUnderlyings) { this(Arrays.asList(otherUnderlyings), cacheManager); } public CombiningLiveDataServer(Collection<? extends StandardLiveDataServer> otherUnderlyings, CacheManager cacheManager) { super(cacheManager); _underlyings = Sets.newHashSet(); _underlyings.addAll(otherUnderlyings); } @Override public int expireSubscriptions() { int expired = 0; for (StandardLiveDataServer server : _underlyings) { expired += server.expireSubscriptions(); } return expired; } @Override protected void startExpirationManager() { // No-op; the underlyings will have their own } @Override protected void stopExpirationManager() { // No-op; the underlyings will have their own } @Override public Collection<LiveDataSubscriptionResponse> subscribe(Collection<LiveDataSpecification> liveDataSpecificationsFromClient, final boolean persistent) { return subscribeByServer( liveDataSpecificationsFromClient, new SubscribeAction() { @Override public Collection<LiveDataSubscriptionResponse> subscribe(StandardLiveDataServer server, Collection<LiveDataSpecification> specifications) { return server.subscribe(specifications, persistent); } @Override public String getName() { return "Subscribe"; } }); } @Override public LiveDataSubscriptionResponseMsg subscriptionRequestMadeImpl(final LiveDataSubscriptionRequest subscriptionRequest) { //Need to override here as well in order to catch the resolution/entitlement checking Collection<LiveDataSubscriptionResponse> responses = subscribeByServer( subscriptionRequest.getSpecifications(), new SubscribeAction() { @Override public Collection<LiveDataSubscriptionResponse> subscribe(StandardLiveDataServer server, Collection<LiveDataSpecification> specifications) { LiveDataSubscriptionRequest liveDataSubscriptionRequest = buildSubRequest(subscriptionRequest, specifications); //NOTE: we call up to subscriptionRequestMade to get the exception catching LiveDataSubscriptionResponseMsg response = server.subscriptionRequestMade(liveDataSubscriptionRequest); //Check that we know how to combine these responses if (response.getRequestingUser() != subscriptionRequest.getUser()) { throw new OpenGammaRuntimeException("Unexpected user in response " + response.getRequestingUser()); } return response.getResponses(); } @Override public String getName() { return "SubscriptionRequestMade"; } }); return new LiveDataSubscriptionResponseMsg(subscriptionRequest.getUser(), responses); } private LiveDataSubscriptionRequest buildSubRequest(final LiveDataSubscriptionRequest subscriptionRequest, Collection<LiveDataSpecification> specifications) { LiveDataSubscriptionRequest liveDataSubscriptionRequest = new LiveDataSubscriptionRequest(subscriptionRequest.getUser(), subscriptionRequest.getType(), specifications); return liveDataSubscriptionRequest; } private interface SubscribeAction { Collection<LiveDataSubscriptionResponse> subscribe(StandardLiveDataServer server, Collection<LiveDataSpecification> specifications); String getName(); } private Collection<LiveDataSubscriptionResponse> subscribeByServer(Collection<LiveDataSpecification> specifications, final SubscribeAction action) { return forEachServer(specifications, new Function<Pair<StandardLiveDataServer, Collection<LiveDataSpecification>>, Collection<LiveDataSubscriptionResponse>>() { @Override public Collection<LiveDataSubscriptionResponse> apply(Pair<StandardLiveDataServer, Collection<LiveDataSpecification>> input) { StandardLiveDataServer specs = input.getFirst(); Collection<LiveDataSpecification> server = input.getSecond(); s_logger.debug("Sending subscription ({}) for {} to underlying server {}", new Object[] {action.getName(), specs, server }); return action.subscribe(specs, server); } }); } private <T> Collection<T> forEachServer(Collection<LiveDataSpecification> specifications, final Function<Pair<StandardLiveDataServer, Collection<LiveDataSpecification>>, Collection<T>> operation) { Map<StandardLiveDataServer, Collection<LiveDataSpecification>> mapped = groupByServer(specifications); Collection<Future<Collection<T>>> futures = new ArrayList<Future<Collection<T>>>(mapped.size()); for (final Entry<StandardLiveDataServer, Collection<LiveDataSpecification>> entry : mapped.entrySet()) { if (entry.getValue().isEmpty()) { continue; } Future<Collection<T>> future = s_subscriptionExecutor.submit(new Callable<Collection<T>>() { @Override public Collection<T> call() throws Exception { return operation.apply(Pairs.of(entry.getKey(), entry.getValue())); } }); futures.add(future); } List<T> responses = new ArrayList<T>(specifications.size()); for (Future<Collection<T>> future : futures) { try { responses.addAll(future.get()); } catch (InterruptedException ex) { //Should be rare, since the subscription methods should bundle everything into the response s_logger.error("Unexpected exception when delegating subscription", ex); throw new OpenGammaRuntimeException(ex.getMessage(), ex); } catch (ExecutionException ex) { //Should be rare, since the subscription methods should bundle everything into the response s_logger.error("Unexpected exception when delegating subscription", ex); throw new OpenGammaRuntimeException(ex.getMessage(), ex); } } return responses; } protected abstract Map<StandardLiveDataServer, Collection<LiveDataSpecification>> groupByServer( Collection<LiveDataSpecification> specs); private StandardLiveDataServer getServer(LiveDataSpecification spec) { Map<StandardLiveDataServer, Collection<LiveDataSpecification>> grouped = groupByServer(Sets.newHashSet(spec)); for (Entry<StandardLiveDataServer, Collection<LiveDataSpecification>> entry : grouped.entrySet()) { if (entry.getValue().size() > 0) { return entry.getKey(); } } throw new OpenGammaRuntimeException("Couldn't find server for " + spec); } @Override public void addSubscriptionListener(SubscriptionListener subscriptionListener) { for (StandardLiveDataServer server : _underlyings) { server.addSubscriptionListener(subscriptionListener); } } @Override public Set<Subscription> getSubscriptions() { Set<Subscription> ret = new HashSet<Subscription>(); for (StandardLiveDataServer server : _underlyings) { Set<Subscription> serversSubscriptions = server.getSubscriptions(); s_logger.debug("Server {} has {} subscriptions", server, serversSubscriptions.size()); ret.addAll(serversSubscriptions); } return ret; } @Override public Subscription getSubscription(LiveDataSpecification fullyQualifiedSpec) { return getServer(fullyQualifiedSpec).getSubscription(fullyQualifiedSpec); } @Override public MarketDataDistributor getMarketDataDistributor(LiveDataSpecification fullyQualifiedSpec) { return getServer(fullyQualifiedSpec).getMarketDataDistributor(fullyQualifiedSpec); } @Override public Map<LiveDataSpecification, MarketDataDistributor> getMarketDataDistributors(Collection<LiveDataSpecification> fullyQualifiedSpecs) { Map<StandardLiveDataServer, Collection<LiveDataSpecification>> grouped = groupByServer(fullyQualifiedSpecs); HashMap<LiveDataSpecification, MarketDataDistributor> ret = new HashMap<LiveDataSpecification, MarketDataDistributor>(); for (Entry<StandardLiveDataServer, Collection<LiveDataSpecification>> entry : grouped.entrySet()) { Map<LiveDataSpecification, MarketDataDistributor> entries = entry.getKey().getMarketDataDistributors(entry.getValue()); ret.putAll(entries); } return ret; } @Override public boolean stopDistributor(MarketDataDistributor distributor) { return getServer(distributor.getFullyQualifiedLiveDataSpecification()).stopDistributor(distributor); } @Override protected void doConnect() { for (StandardLiveDataServer server : _underlyings) { server.start(); } } @Override protected void doDisconnect() { for (StandardLiveDataServer server : _underlyings) { server.stop(); } } //----- Shouldn't happen ----- @Override protected Map<String, Object> doSubscribe(Collection<String> uniqueIds) { throw new IllegalArgumentException(); } @Override protected void doUnsubscribe(Collection<Object> subscriptionHandles) { throw new IllegalArgumentException(); } @Override protected Map<String, FudgeMsg> doSnapshot(Collection<String> uniqueIds) { throw new IllegalArgumentException(); } @Override protected ExternalScheme getUniqueIdDomain() { throw new IllegalArgumentException(); } @Override protected boolean snapshotOnSubscriptionStartRequired(Subscription subscription) { throw new IllegalArgumentException(); } }