/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.master.marketdatasnapshot.impl;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableList;
import com.opengamma.DataNotFoundException;
import com.opengamma.core.change.ChangeEvent;
import com.opengamma.core.change.ChangeListener;
import com.opengamma.core.marketdatasnapshot.MarketDataSnapshotChangeListener;
import com.opengamma.core.marketdatasnapshot.MarketDataSnapshotSource;
import com.opengamma.core.marketdatasnapshot.NamedSnapshot;
import com.opengamma.id.ObjectId;
import com.opengamma.id.UniqueId;
import com.opengamma.id.VersionCorrection;
import com.opengamma.master.AbstractMasterSource;
import com.opengamma.master.marketdatasnapshot.MarketDataSnapshotDocument;
import com.opengamma.master.marketdatasnapshot.MarketDataSnapshotMaster;
import com.opengamma.master.marketdatasnapshot.MarketDataSnapshotSearchRequest;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.PublicSPI;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
/**
* A {@code MarketDataSnapshotSource} implemented using an underlying {@code MarketDataSnapshotMaster}.
* <p>
* The {@link MarketDataSnapshotSource} interface provides snapshots to the engine via a narrow API.
* This class provides the source on top of a standard {@link MarketDataSnapshotMaster}.
*/
@PublicSPI
public class MasterSnapshotSource
extends AbstractMasterSource<NamedSnapshot, MarketDataSnapshotDocument, MarketDataSnapshotMaster>
implements MarketDataSnapshotSource {
/**
* The listeners.
*/
private final ConcurrentMap<Pair<UniqueId, MarketDataSnapshotChangeListener>, ChangeListener> _registeredListeners =
new ConcurrentHashMap<>();
/**
* Creates an instance with an underlying master which does not override versions.
*
* @param master the master, not null
*/
public MasterSnapshotSource(final MarketDataSnapshotMaster master) {
super(master);
}
//-------------------------------------------------------------------------
@Override
public void addChangeListener(final UniqueId uniqueId, final MarketDataSnapshotChangeListener listener) {
ChangeListener changeListener = new ChangeListener() {
@Override
public void entityChanged(ChangeEvent event) {
ObjectId changedId = event.getObjectId();
if (changedId != null && changedId.getScheme().equals(uniqueId.getScheme()) &&
changedId.getValue().equals(uniqueId.getValue())) {
//TODO This is over cautious in the case of corrections to non latest versions
listener.objectChanged(uniqueId.getObjectId());
}
}
};
_registeredListeners.put(Pairs.of(uniqueId, listener), changeListener);
getMaster().changeManager().addChangeListener(changeListener);
}
@Override
public void removeChangeListener(UniqueId uid, MarketDataSnapshotChangeListener listener) {
ChangeListener changeListener = _registeredListeners.remove(Pairs.of(uid, listener));
getMaster().changeManager().removeChangeListener(changeListener);
}
@Override
public <S extends NamedSnapshot> S getSingle(Class<S> type,
String snapshotName,
VersionCorrection versionCorrection) {
// Try to find an exact match using the name and type first. If this doesn't work
// (perhaps as the type searched for is a superclass of the type held), we search
// again by name only and check the type after.
// TODO - review usage patterns, do we normally hit or miss using type. If names are generally unique searching by type is potentially redundant
TypedSnapshotSearcher<S> searcher =
new TypedSnapshotSearcher<>(getMaster(), type, snapshotName, versionCorrection);
return searcher.search();
}
/**
* Helper class which assists in the search for snapshots. It first searches using a
* name and expected type explicitly. If that does not succeed, it then searches by
* name and checks the type afterwards. This is primarily to support the case where
* a search is made using a supertype (e.g. interface) but the database holds the
* implementation type.
*
* @param <S> the type of snapshot being searched for
*/
private static final class TypedSnapshotSearcher<S extends NamedSnapshot> {
/**
* Logger for the class.
*/
private static final Logger s_logger = LoggerFactory.getLogger(TypedSnapshotSearcher.class);
/**
* The master to search for data in.
*/
private final MarketDataSnapshotMaster _master;
/**
* The type of snapshot being searched for.
*/
private final Class<S> _type;
/**
* The name of the snapshot being searched for.
*/
private final String _snapshotName;
/**
* The version correction of the snapshot being searched for.
*/
private final VersionCorrection _versionCorrection;
/**
* Create a searcher configured with the details of what to search for.
*
* @param master the master to search in, not null
* @param type the type of snapshot being searched for
* @param snapshotName the name of the snapshot being searched for
* @param versionCorrection the version correction of the snapshot being searched for
*/
public TypedSnapshotSearcher(MarketDataSnapshotMaster master,
Class<S> type,
String snapshotName,
VersionCorrection versionCorrection) {
_master = ArgumentChecker.notNull(master, "master");
_type = ArgumentChecker.notNull(type, "type");
_snapshotName = ArgumentChecker.notNull(snapshotName, "snapshotName");
_versionCorrection = ArgumentChecker.notNull(versionCorrection, "versionCorrection");
}
/**
* Attempt to find a snapshot with the name and type specified. If no match
* found initially, search with the name for a snapshot which is type
* compatible with the type requested.
*
* @return a matching snapshot, not null
* @throws DataNotFoundException if no matching snapshot can be found
*/
public S search() {
S result = findWithMatchingType();
return result != null ? result : findWithGeneralType();
}
private S findWithMatchingType() {
MarketDataSnapshotSearchRequest request = createBaseSearchRequest();
request.setType(_type);
return selectResult(_master.search(request).getNamedSnapshots(), true);
}
private S findWithGeneralType() {
MarketDataSnapshotSearchRequest request = createBaseSearchRequest();
S result = selectResult(_master.search(request).getNamedSnapshots(), false);
if (result != null) {
return result;
} else {
throw new DataNotFoundException("No snapshot found with type: [" + _type.getName() + "], id: [" +
_snapshotName + "] and versionCorrection: [" + _versionCorrection + "]");
}
}
private MarketDataSnapshotSearchRequest createBaseSearchRequest() {
MarketDataSnapshotSearchRequest request = new MarketDataSnapshotSearchRequest();
request.setName(_snapshotName);
request.setVersionCorrection(_versionCorrection);
return request;
}
private S selectResult(List<NamedSnapshot> results, boolean warnOnTypeMismatch) {
List<S> filtered = filterForCorrectType(results, warnOnTypeMismatch);
if (filtered.size() < results.size()) {
s_logger.info("Filtered out {} snapshot(s) where type is not: {}", results.size() - filtered.size(), _type);
}
return selectFirst(filtered);
}
private List<S> filterForCorrectType(List<NamedSnapshot> results, boolean warnOnTypeMismatch) {
ImmutableList.Builder<S> builder = ImmutableList.builder();
for (NamedSnapshot snapshot : results) {
if (_type.isAssignableFrom(snapshot.getClass())) {
builder.add(_type.cast(snapshot));
} else if (warnOnTypeMismatch) {
s_logger.warn("Found matching snapshot with expected type: {} and name: {} - but actual type was: {}",
_type.getName(), _snapshotName, snapshot.getClass().getName());
}
}
return builder.build();
}
private S selectFirst(List<S> filtered) {
if (filtered.isEmpty()) {
return null;
}
if (filtered.size() > 1) {
s_logger.warn("Found multiple matching snapshot results for type: {} and name: {} - returning first match found",
_type.getName(), _snapshotName);
}
return filtered.get(0);
}
}
}