Package com.opengamma.core.security.impl

Source Code of com.opengamma.core.security.impl.CoalescingSecuritySource

/**
* 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;

/**
* 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(Pair.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(Pair.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);
  }

}
TOP

Related Classes of com.opengamma.core.security.impl.CoalescingSecuritySource

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.