/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.view.worker;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.engine.ComputationTarget;
import com.opengamma.engine.ComputationTargetResolver;
import com.opengamma.engine.depgraph.DependencyNode;
import com.opengamma.engine.depgraph.impl.RootDiscardingSubgrapher;
import com.opengamma.engine.function.MarketDataAliasingFunction;
import com.opengamma.engine.function.MarketDataSourcingFunction;
import com.opengamma.engine.marketdata.availability.MarketDataAvailabilityProvider;
import com.opengamma.engine.value.ValuePropertyNames;
import com.opengamma.engine.value.ValueRequirement;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.util.PoolExecutor;
/**
* Filters a dependency graph to exclude any market data sourcing nodes from a previous data provider that are not valid for the new provider.
*/
/* package */final class InvalidMarketDataDependencyNodeFilter extends RootDiscardingSubgrapher {
private static final Logger s_logger = LoggerFactory.getLogger(InvalidMarketDataDependencyNodeFilter.class);
private final ComputationTargetResolver.AtVersionCorrection _targetResolver;
private final MarketDataAvailabilityProvider _marketData;
/**
* The market data specifications from the leaves of the graph. Mapped to {@link Boolean#TRUE} if the value is valid, mapped to {@link Boolean#FALSE} if not.
*/
private final Map<ValueSpecification, Boolean> _valid = new ConcurrentHashMap<ValueSpecification, Boolean>();
/**
* The market data specifications to check. This is a map from an aliased market data value to the underlying market data value values. If the value is used in an unaliased form then the key and
* value are the same for an entry.
*/
private Map<ValueSpecification, ValueSpecification> _toCheck = new HashMap<ValueSpecification, ValueSpecification>();
/**
* Flag to indicate that there is at least one invalid node.
*/
private volatile boolean _invalidNodes;
public InvalidMarketDataDependencyNodeFilter(final ComputationTargetResolver.AtVersionCorrection targetResolver, final MarketDataAvailabilityProvider marketData) {
_targetResolver = targetResolver;
_marketData = marketData;
}
/**
* Updates the "to-check" list with market data from the given graph. This must be called for all graph fragments that this sub-grapher will be required to act on before {@link #checkMarketData} is
* called.
*
* @param node a root node of the graph to consider, not null
* @param terminalOutputs the graph's terminal outputs, not null
* @param visited the "visited" buffer, not null
*/
public void init(final DependencyNode node, final Map<ValueSpecification, ?> terminalOutputs, final Set<DependencyNode> visited) {
if (!visited.add(node)) {
return;
}
final int count = node.getInputCount();
if (count == 1) {
if (MarketDataAliasingFunction.UNIQUE_ID.equals(node.getFunction().getFunctionId())) {
final ValueSpecification value = node.getInputValue(0);
if (terminalOutputs.containsKey(value)) {
// The underlying data is used directly as a terminal-output
_toCheck.put(value, value);
}
final int outputs = node.getOutputCount();
for (int i = 0; i < outputs; i++) {
final ValueSpecification aliased = node.getOutputValue(i);
_toCheck.put(aliased, value);
}
return;
}
} else if (count == 0) {
if (MarketDataSourcingFunction.UNIQUE_ID.equals(node.getFunction().getFunctionId())) {
// The value is consumed other than via a market-data-alias
assert node.getOutputCount() == 1;
final ValueSpecification value = node.getOutputValue(0);
_toCheck.put(value, value);
}
return;
}
for (int i = 0; i < count; i++) {
init(node.getInputNode(i), terminalOutputs, visited);
}
}
/**
* Tests whether the alias used in the graph still resolves to a usable target and the market data provider satisfies the requirement with the same underlying market data.
* <p>
* This may be called by multiple threads and will update the {@link #_valid} and {@link #_invalidNodes} properties.
*
* @param alias the alias (might be the same as {@code marketData} if it is used directly), not null
* @param marketData the previous market data specification returned by the data provider, not null
*/
private void check(final ValueSpecification alias, final ValueSpecification marketData) {
final ComputationTarget target = _targetResolver.resolve(alias.getTargetSpecification());
if (target == null) {
// This shouldn't normally happen (a default target specification will always be created that gives a stub Primitive instance) unless
// the target specification cannot be resolved by the target resolver any more.
s_logger.warn("Couldn't resolve {}", alias.getTargetSpecification());
_valid.put(alias, Boolean.FALSE);
_invalidNodes = true;
return;
}
final Object targetValue = target.getValue();
final ValueRequirement desiredValue = new ValueRequirement(alias.getValueName(), alias.getTargetSpecification(), alias.getProperties().withoutAny(ValuePropertyNames.DATA_PROVIDER));
final ValueSpecification requiredMarketData = _marketData.getAvailability(alias.getTargetSpecification(), targetValue, desiredValue);
if (marketData.equals(requiredMarketData)) {
s_logger.debug("Market data entry {} still available for {}", marketData, desiredValue);
_valid.put(alias, Boolean.TRUE);
} else {
s_logger.debug("New market data {} required for {}", requiredMarketData, desiredValue);
_valid.put(alias, Boolean.FALSE);
_invalidNodes = true;
}
}
private final class CheckBatch implements Runnable {
private final ValueSpecification[] _data;
public CheckBatch(final Iterator<Map.Entry<ValueSpecification, ValueSpecification>> itr, final int count) {
_data = new ValueSpecification[count * 2];
int j = 0;
for (int i = 0; i < count; i++) {
final Map.Entry<ValueSpecification, ValueSpecification> e = itr.next();
_data[j++] = e.getKey();
_data[j++] = e.getValue();
}
}
@Override
public void run() {
int i = 0;
do {
final ValueSpecification alias = _data[i++];
final ValueSpecification marketData = _data[i++];
check(alias, marketData);
} while (i < _data.length);
}
}
/**
* After the "to-check" list is populated by calling {@link #init}, call this to check the market data. Checks are submitted to an executor in batches for parallel operation.
*
* @param executor the executor to use, not null
* @param batchSize the number of items to check in each batch
* @return true if at least one market data node is now invalid, false if all are okay
*/
public boolean checkMarketData(final PoolExecutor executor, final int batchSize) {
final PoolExecutor.Service<?> service = executor.createService(null);
Map<ValueSpecification, ValueSpecification> toCheck = _toCheck;
_toCheck = null;
int count = toCheck.size();
final Iterator<Map.Entry<ValueSpecification, ValueSpecification>> itr = toCheck.entrySet().iterator();
while (count > 0) {
if (count <= batchSize) {
while (itr.hasNext()) {
final Map.Entry<ValueSpecification, ValueSpecification> e = itr.next();
check(e.getKey(), e.getValue());
}
break;
} else {
service.execute(new CheckBatch(itr, batchSize));
count -= batchSize;
}
}
try {
service.join();
} catch (InterruptedException e) {
throw new OpenGammaRuntimeException("Interrupted", e);
}
return _invalidNodes;
}
// RootDiscardingSubgrapher
@Override
public boolean acceptNode(final DependencyNode node) {
int count = node.getInputCount();
if (count == 1) {
if (!MarketDataAliasingFunction.UNIQUE_ID.equals(node.getFunction().getFunctionId())) {
return true;
}
// Only consider the aliasing function
} else if (count == 0) {
if (!MarketDataSourcingFunction.UNIQUE_ID.equals(node.getFunction().getFunctionId())) {
return true;
}
// Only consider the sourcing function
} else {
return true;
}
count = node.getOutputCount();
for (int i = 0; i < count; i++) {
if (Boolean.TRUE != _valid.get(node.getOutputValue(i))) {
return false;
}
}
return true;
}
}