/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.core.security.impl;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.opengamma.DataNotFoundException;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.core.change.ChangeManager;
import com.opengamma.core.security.Security;
import com.opengamma.core.security.SecuritySource;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.id.ObjectId;
import com.opengamma.id.UniqueId;
import com.opengamma.id.VersionCorrection;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
/**
* Wrapper around an existing {@link SecuritySource} that coalesces concurrent calls into a single call to one of the bulk operation methods on the underlying. This can improve efficiency where the
* underlying uses network resources and the round trip of multiple single calls is less desirable than a single bulk call.
*/
public class CoalescingSecuritySource implements SecuritySource {
private final SecuritySource _underlying;
private abstract static class Callback {
private int _expected;
public Callback(final int expected) {
_expected = expected;
}
protected abstract void store(final UniqueId uid, final Security security);
public synchronized void found(final UniqueId uid, final Security security) {
store(uid, security);
if (--_expected <= 0) {
notify();
}
}
public synchronized void missed() {
if (--_expected <= 0) {
notify();
}
}
/**
* Blocks until all expected values are out, or the write lock could be claimed.
*/
public synchronized boolean waitForResult(final AtomicBoolean writing) {
try {
while ((_expected > 0) && !writing.compareAndSet(false, true)) {
wait();
}
return _expected <= 0;
} catch (InterruptedException e) {
throw new OpenGammaRuntimeException("Interrupted", e);
}
}
public synchronized void release() {
notify();
}
}
private static class SingleCallback extends Callback {
private Security _security;
public SingleCallback() {
super(1);
}
@Override
protected void store(final UniqueId uid, final Security security) {
_security = security;
}
public synchronized Security getSecurity() {
return _security;
}
}
private static class MultipleCallback extends Callback {
private final Map<UniqueId, Security> _result;
public MultipleCallback(final int expected) {
super(expected);
_result = Maps.newHashMapWithExpectedSize(expected);
}
@Override
protected void store(final UniqueId uid, final Security security) {
_result.put(uid, security);
}
public synchronized Map<UniqueId, Security> getSecurities() {
return _result;
}
}
private final AtomicBoolean _fetching = new AtomicBoolean();
private final Queue<Pair<UniqueId, ? extends Callback>> _pending = new ConcurrentLinkedQueue<Pair<UniqueId, ? extends Callback>>();
public CoalescingSecuritySource(final SecuritySource underlying) {
_underlying = underlying;
}
protected SecuritySource getUnderlying() {
return _underlying;
}
@Override
public ChangeManager changeManager() {
return getUnderlying().changeManager();
}
private Collection<Pair<UniqueId, ? extends Callback>> drainPending() {
final Collection<Pair<UniqueId, ? extends Callback>> pending = new LinkedList<Pair<UniqueId, ? extends Callback>>();
Pair<UniqueId, ? extends Callback> entry = _pending.poll();
while (entry != null) {
pending.add(entry);
entry = _pending.poll();
}
return pending;
}
private void addPendingToRequest(final Collection<Pair<UniqueId, ? extends Callback>> pending, final Set<UniqueId> request) {
for (Pair<UniqueId, ? extends Callback> pendingEntry : pending) {
request.add(pendingEntry.getFirst());
}
}
private void notifyPending(final Collection<Pair<UniqueId, ? extends Callback>> pending, final Map<UniqueId, Security> result) {
for (Pair<UniqueId, ? extends Callback> pendingEntry : pending) {
final Security security = result.get(pendingEntry.getFirst());
if (security != null) {
pendingEntry.getSecond().found(pendingEntry.getFirst(), security);
} else {
pendingEntry.getSecond().missed();
}
}
}
private void errorPending(final Collection<Pair<UniqueId, ? extends Callback>> pending) {
for (Pair<UniqueId, ? extends Callback> pendingEntry : pending) {
pendingEntry.getSecond().missed();
}
}
protected void releaseOtherWritingThreads() {
final Pair<UniqueId, ? extends Callback> otherThread = _pending.peek();
if (otherThread != null) {
// Notify the thread that it might be able to claim the write lock
otherThread.getSecond().release();
}
}
@Override
public Security get(final UniqueId uniqueId) {
if (!_fetching.compareAndSet(false, true)) {
final SingleCallback callback = new SingleCallback();
_pending.add(Pairs.of(uniqueId, callback));
if (callback.waitForResult(_fetching)) {
return callback.getSecurity();
}
// Request the pending queue
final Collection<Pair<UniqueId, ? extends Callback>> pending = drainPending();
final Set<UniqueId> request = Sets.newHashSetWithExpectedSize(pending.size());
addPendingToRequest(pending, request);
final Map<UniqueId, Security> fullResult;
try {
fullResult = getUnderlying().get(request);
notifyPending(pending, fullResult);
} catch (RuntimeException t) {
errorPending(pending);
throw t;
} finally {
_fetching.set(false);
releaseOtherWritingThreads();
}
// We've either notified our own callback or another thread has already done it
return callback.getSecurity();
} else {
Pair<UniqueId, ? extends Callback> e = _pending.poll();
if (e == null) {
// Single request
Security security = null;
try {
security = getUnderlying().get(uniqueId);
} catch (DataNotFoundException ex) {
// Ignore
} finally {
_fetching.set(false);
releaseOtherWritingThreads();
}
return security;
} else {
// Single request, e and the content of the pending queue
final Collection<Pair<UniqueId, ? extends Callback>> pending = drainPending();
pending.add(e);
final Set<UniqueId> request = Sets.newHashSetWithExpectedSize(pending.size() + 1);
request.add(uniqueId);
addPendingToRequest(pending, request);
final Map<UniqueId, Security> fullResult;
try {
fullResult = getUnderlying().get(request);
notifyPending(pending, fullResult);
} catch (RuntimeException t) {
errorPending(pending);
throw t;
} finally {
_fetching.set(false);
releaseOtherWritingThreads();
}
return fullResult.get(uniqueId);
}
}
}
@Override
public Map<UniqueId, Security> get(final Collection<UniqueId> uniqueIds) {
if (!_fetching.compareAndSet(false, true)) {
final MultipleCallback callback = new MultipleCallback(uniqueIds.size());
for (UniqueId uniqueId : uniqueIds) {
_pending.add(Pairs.of(uniqueId, callback));
}
if (callback.waitForResult(_fetching)) {
return callback.getSecurities();
}
// Request the pending queue
final Collection<Pair<UniqueId, ? extends Callback>> pending = drainPending();
final Set<UniqueId> request = Sets.newHashSetWithExpectedSize(pending.size());
addPendingToRequest(pending, request);
final Map<UniqueId, Security> fullResult;
try {
fullResult = getUnderlying().get(request);
notifyPending(pending, fullResult);
} catch (RuntimeException t) {
errorPending(pending);
throw t;
} finally {
_fetching.set(false);
releaseOtherWritingThreads();
}
// We've either notified our own callback or another thread has already done it
return callback.getSecurities();
} else {
Pair<UniqueId, ? extends Callback> e = _pending.poll();
if (e == null) {
// Direct request
final Map<UniqueId, Security> result;
try {
result = getUnderlying().get(uniqueIds);
} finally {
_fetching.set(false);
releaseOtherWritingThreads();
}
return result;
} else {
// Bulk request, e and the content of the pending queue
final Collection<Pair<UniqueId, ? extends Callback>> pending = drainPending();
pending.add(e);
final Set<UniqueId> request = Sets.newHashSetWithExpectedSize(pending.size() + uniqueIds.size());
request.addAll(uniqueIds);
addPendingToRequest(pending, request);
final Map<UniqueId, Security> fullResult;
try {
fullResult = getUnderlying().get(request);
notifyPending(pending, fullResult);
} catch (RuntimeException t) {
errorPending(pending);
throw t;
} finally {
_fetching.set(false);
releaseOtherWritingThreads();
}
final Map<UniqueId, Security> result = Maps.newHashMapWithExpectedSize(uniqueIds.size());
for (UniqueId uniqueId : uniqueIds) {
final Security security = fullResult.get(uniqueId);
if (security != null) {
result.put(uniqueId, security);
}
}
return result;
}
}
}
@Override
public Security get(final ObjectId objectId, final VersionCorrection versionCorrection) {
return getUnderlying().get(objectId, versionCorrection);
}
@Override
public Map<ObjectId, Security> get(final Collection<ObjectId> objectIds, final VersionCorrection versionCorrection) {
return getUnderlying().get(objectIds, versionCorrection);
}
@Override
public Collection<Security> get(final ExternalIdBundle bundle, final VersionCorrection versionCorrection) {
return getUnderlying().get(bundle, versionCorrection);
}
@Override
public Map<ExternalIdBundle, Collection<Security>> getAll(final Collection<ExternalIdBundle> bundles, final VersionCorrection versionCorrection) {
return getUnderlying().getAll(bundles, versionCorrection);
}
@Override
public Collection<Security> get(final ExternalIdBundle bundle) {
return getUnderlying().get(bundle);
}
@Override
public Security getSingle(final ExternalIdBundle bundle) {
return getUnderlying().getSingle(bundle);
}
@Override
public Security getSingle(final ExternalIdBundle bundle, final VersionCorrection versionCorrection) {
return getUnderlying().getSingle(bundle, versionCorrection);
}
@Override
public Map<ExternalIdBundle, Security> getSingle(final Collection<ExternalIdBundle> bundles, final VersionCorrection versionCorrection) {
return getUnderlying().getSingle(bundles, versionCorrection);
}
}