com.opengamma.core.security.impl.CoalescingSecuritySource.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.core.security.impl.CoalescingSecuritySource.java

Source

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

}