/**
* Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.depgraph;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Sets;
import com.opengamma.engine.ComputationTarget;
import com.opengamma.engine.ComputationTargetSpecification;
import com.opengamma.engine.function.CompiledFunctionDefinition;
import com.opengamma.engine.function.MarketDataAliasingFunction;
import com.opengamma.engine.function.MarketDataSourcingFunction;
import com.opengamma.engine.function.ParameterizedFunction;
import com.opengamma.engine.marketdata.availability.MarketDataNotSatisfiableException;
import com.opengamma.engine.target.ComputationTargetReferenceVisitor;
import com.opengamma.engine.target.ComputationTargetRequirement;
import com.opengamma.engine.target.lazy.LazyComputationTargetResolver;
import com.opengamma.engine.value.ValueProperties;
import com.opengamma.engine.value.ValuePropertyNames;
import com.opengamma.engine.value.ValueRequirement;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.util.async.BlockingOperation;
import com.opengamma.util.tuple.Triple;
/* package */final class GetFunctionsStep extends ResolveTask.State {
private static final Logger s_logger = LoggerFactory.getLogger(GetFunctionsStep.class);
private static final ParameterizedFunction MARKET_DATA_SOURCING_FUNCTION = createParameterizedFunction(MarketDataSourcingFunction.INSTANCE);
private static final ParameterizedFunction RELABELLING_FUNCTION = createParameterizedFunction(MarketDataAliasingFunction.INSTANCE);
private static ParameterizedFunction createParameterizedFunction(final CompiledFunctionDefinition function) {
return new ParameterizedFunction(function, function.getFunctionDefinition().getDefaultParameters());
}
public GetFunctionsStep(final ResolveTask task) {
super(task);
}
private static final ComputationTargetReferenceVisitor<Object> s_getTargetValue = new ComputationTargetReferenceVisitor<Object>() {
@Override
public Object visitComputationTargetRequirement(final ComputationTargetRequirement requirement) {
return requirement.getIdentifiers();
}
@Override
public Object visitComputationTargetSpecification(final ComputationTargetSpecification specification) {
// If the object couldn't be resolved, then the rogue UID is the best we can present
return specification.getUniqueId();
}
};
/**
* Removes any optional flags on constraints. If the constraint is optional and the provider produced no value, then the constraint is removed. If the provider produced values for the constraint,
* and an intersection exists, then the intersection is used. Otherwise the maximal value set is used to satisfy the original constraint.
*
* @param constraints the requested constraints, not null
* @param properties the provider's value specification properties, not null
* @return the builder for constraints
*/
private static ValueProperties.Builder intersectOptional(final ValueProperties constraints, final ValueProperties properties) {
ValueProperties.Builder builder = constraints.copy();
// TODO: [PLAT-3446] Return the builder immediately to recreate the observed fault
for (String property : constraints.getProperties()) {
final Set<String> providerValues = properties.getValues(property);
if (providerValues != null) {
// Remove optional flag and take intersection if there is one
builder.notOptional(property);
final Set<String> constrainedValues = constraints.getValues(property);
if (!providerValues.isEmpty()) {
if (constrainedValues.isEmpty()) {
builder.with(property, providerValues);
} else {
final Set<String> intersection = Sets.intersection(constrainedValues, providerValues);
if (!intersection.isEmpty()) {
builder.withoutAny(property).with(property, intersection);
}
}
}
} else {
if (constraints.isOptional(property)) {
// Constraint is optional, remove
builder.withoutAny(property);
}
}
}
return builder;
}
@Override
protected boolean run(final GraphBuildingContext context) {
final ValueRequirement requirement = getValueRequirement();
boolean missing = false;
ValueSpecification marketDataSpec = null;
ComputationTargetSpecification targetSpec = null;
ComputationTarget target = null;
Object targetValue = null;
BlockingOperation.off();
try {
targetSpec = getTargetSpecification(context);
if (targetSpec != null) {
target = LazyComputationTargetResolver.resolve(context.getCompilationContext().getComputationTargetResolver(), targetSpec);
if (target != null) {
targetValue = target.getValue();
} else {
targetValue = requirement.getTargetReference().accept(s_getTargetValue);
}
} else {
targetValue = requirement.getTargetReference().accept(s_getTargetValue);
}
marketDataSpec = context.getMarketDataAvailabilityProvider().getAvailability(targetSpec, targetValue, requirement);
} catch (final BlockingOperation e) {
return false;
} catch (final MarketDataNotSatisfiableException e) {
missing = true;
} finally {
BlockingOperation.on();
}
if (marketDataSpec != null) {
s_logger.info("Found live data for {}", requirement);
marketDataSpec = context.simplifyType(marketDataSpec);
if (targetSpec == null) {
// The system resolver did not produce a target that we can monitor, so use the MDAP supplied value
targetSpec = marketDataSpec.getTargetSpecification();
}
ResolvedValue resolvedValue = createResult(marketDataSpec, MARKET_DATA_SOURCING_FUNCTION, Collections.<ValueSpecification>emptySet(), Collections.singleton(marketDataSpec));
final ValueProperties constraints = requirement.getConstraints();
boolean constraintsSatisfied = constraints.isSatisfiedBy(marketDataSpec.getProperties());
if ((requirement.getValueName() != marketDataSpec.getValueName())
|| !targetSpec.equals(marketDataSpec.getTargetSpecification())
|| !constraintsSatisfied) {
// The specification returned by market data provision does not match the logical target; publish a substitute node
context.declareProduction(resolvedValue);
final ValueProperties properties;
if (constraintsSatisfied) {
// Just the target or value name that was a mismatch; use the provider properties
properties = marketDataSpec.getProperties();
} else {
final Set<String> functionNames = constraints.getValues(ValuePropertyNames.FUNCTION);
if (functionNames == null) {
final Set<String> allProperties = constraints.getProperties();
if (allProperties == null) {
// Requirement made no constraints
properties = ValueProperties.with(ValuePropertyNames.FUNCTION, MarketDataAliasingFunction.UNIQUE_ID).get();
} else if (!allProperties.isEmpty()) {
// Requirement made no constraint on function identifier
properties = intersectOptional(constraints, marketDataSpec.getProperties()).with(ValuePropertyNames.FUNCTION, MarketDataAliasingFunction.UNIQUE_ID).get();
} else {
// Requirement used a nearly infinite property bundle that omitted a function identifier
properties = constraints.copy().withAny(ValuePropertyNames.FUNCTION).get();
}
} else {
if (functionNames.isEmpty()) {
final Set<String> allProperties = constraints.getProperties();
if (allProperties.isEmpty()) {
// Requirement is for an infinite or nearly infinite property bundle. This is valid but may be indicative of an error
properties = constraints;
} else {
// Requirement had a wild card for the function but is otherwise finite
properties = intersectOptional(constraints, marketDataSpec.getProperties()).withoutAny(ValuePropertyNames.FUNCTION)
.with(ValuePropertyNames.FUNCTION, MarketDataAliasingFunction.UNIQUE_ID).get();
}
} else if (functionNames.size() == 1) {
// Requirement is fully specified
properties = intersectOptional(constraints, marketDataSpec.getProperties()).get();
} else {
// Requirement allowed a choice of function - pick one
properties = intersectOptional(constraints, marketDataSpec.getProperties()).withoutAny(ValuePropertyNames.FUNCTION).with(ValuePropertyNames.FUNCTION, functionNames.iterator().next())
.get();
}
}
}
final ValueSpecification relabelledSpec = new ValueSpecification(requirement.getValueName(), targetSpec, properties);
resolvedValue = createResult(relabelledSpec, RELABELLING_FUNCTION, Collections.singleton(marketDataSpec), Collections.singleton(relabelledSpec));
}
final ResolvedValueProducer producer = new SingleResolvedValueProducer(requirement, resolvedValue);
final ResolvedValueProducer existing = context.declareTaskProducing(resolvedValue.getValueSpecification(), getTask(), producer);
if (existing == producer) {
context.declareProduction(resolvedValue);
if (!pushResult(context, resolvedValue, true)) {
throw new IllegalStateException(resolvedValue + " rejected by pushResult");
}
} else {
producer.release(context);
existing.addCallback(context, new ResolvedValueCallback() {
@Override
public void resolved(final GraphBuildingContext context, final ValueRequirement valueRequirement, final ResolvedValue resolvedValue, final ResolutionPump pump) {
if (pump != null) {
pump.close(context);
}
if (!pushResult(context, resolvedValue, true)) {
throw new IllegalStateException(resolvedValue + " rejected by pushResult");
}
// Leave in current state; will go to finished after being pumped
}
@Override
public void failed(final GraphBuildingContext context, final ValueRequirement value, final ResolutionFailure failure) {
storeFailure(failure);
setTaskStateFinished(context);
}
@Override
public void recursionDetected() {
getTask().setRecursionDetected();
}
});
existing.release(context);
}
// Leave in current state; will go to finished after being pumped
} else {
if (missing) {
s_logger.info("Missing market data for {}", requirement);
storeFailure(context.marketDataMissing(requirement));
setTaskStateFinished(context);
} else {
if (target != null) {
final GraphBuildingContext.ResolutionIterator existingResolutions = context.getResolutions(targetSpec, requirement.getValueName());
if (existingResolutions != null) {
setRunnableTaskState(new TargetDigestStep(getTask(), existingResolutions), context);
} else {
getFunctions(target, context, this);
}
} else {
s_logger.info("No functions for unresolved target {}", requirement);
storeFailure(context.couldNotResolve(requirement));
setTaskStateFinished(context);
}
}
}
return true;
}
protected static void getFunctions(final ComputationTarget target, final GraphBuildingContext context, final ResolveTask.State state) {
final ValueRequirement requirement = state.getValueRequirement();
final Iterator<Triple<ParameterizedFunction, ValueSpecification, Collection<ValueSpecification>>> itr = context.getFunctionResolver().resolveFunction(
requirement.getValueName(), target, requirement.getConstraints());
if (itr.hasNext()) {
s_logger.debug("Found functions for {}", requirement);
state.setRunnableTaskState(new ResolvedFunctionStep(state.getTask(), itr), context);
} else {
s_logger.info("No functions for {}", requirement);
state.storeFailure(context.noFunctions(requirement));
state.setTaskStateFinished(context);
}
}
@Override
protected void pump(final GraphBuildingContext context) {
// Only had one market data result so go to finished state
setTaskStateFinished(context);
}
@Override
public String toString() {
return "GET_FUNCTIONS" + getObjectId();
}
}