/** * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.financial.analytics; import java.util.Collections; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.threeten.bp.Instant; import com.google.common.collect.Sets; import com.opengamma.engine.ComputationTarget; import com.opengamma.engine.function.AbstractFunction; import com.opengamma.engine.function.CompiledFunctionDefinition; import com.opengamma.engine.function.FunctionCompilationContext; import com.opengamma.engine.function.FunctionDefinition; import com.opengamma.engine.function.FunctionExecutionContext; import com.opengamma.engine.function.FunctionInputs; import com.opengamma.engine.function.FunctionInvoker; import com.opengamma.engine.function.FunctionParameters; import com.opengamma.engine.target.ComputationTargetType; import com.opengamma.engine.value.ComputedValue; 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.ArgumentChecker; import com.opengamma.util.async.AsynchronousExecution; import com.opengamma.util.async.AsynchronousOperation; import com.opengamma.util.async.AsynchronousResult; import com.opengamma.util.async.ResultListener; /** * Wraps another function definition into a form that can work with one or more of its inputs missing. */ public class MissingInputsFunction extends AbstractFunction implements CompiledFunctionDefinition, FunctionInvoker { private static final Logger s_logger = LoggerFactory.getLogger(MissingInputsFunction.class); /** * Value of the {@link ValuePropertyNames#AGGREGATION} property when one or more of the inputs may be missing. */ public static final String AGGREGATION_STYLE_MISSING = "MissingInputs"; /** * Value of the {@link ValuePropertyNames#AGGREGATION} property when all of the inputs must be available. */ public static final String AGGREGATION_STYLE_FULL = "Full"; private final FunctionDefinition _underlyingDefinition; private final CompiledFunctionDefinition _underlyingCompiled; private final FunctionInvoker _underlyingInvoker; public MissingInputsFunction(final FunctionDefinition underlying) { ArgumentChecker.notNull(underlying, "underlying"); _underlyingDefinition = underlying; if (underlying instanceof CompiledFunctionDefinition) { _underlyingCompiled = (CompiledFunctionDefinition) underlying; if (underlying instanceof FunctionInvoker) { _underlyingInvoker = (FunctionInvoker) underlying; } else { _underlyingInvoker = null; } } else { _underlyingCompiled = null; _underlyingInvoker = null; } } protected MissingInputsFunction(final CompiledFunctionDefinition underlying) { ArgumentChecker.notNull(underlying, "underlying"); _underlyingDefinition = underlying.getFunctionDefinition(); _underlyingCompiled = underlying; if (underlying instanceof FunctionInvoker) { _underlyingInvoker = (FunctionInvoker) underlying; } else { _underlyingInvoker = null; } } protected MissingInputsFunction(final FunctionInvoker underlying) { ArgumentChecker.notNull(underlying, "underlying"); _underlyingDefinition = null; _underlyingCompiled = null; _underlyingInvoker = underlying; } protected MissingInputsFunction create(final CompiledFunctionDefinition underlying) { return new MissingInputsFunction(underlying); } protected MissingInputsFunction create(final FunctionInvoker underlying) { return new MissingInputsFunction(underlying); } protected FunctionDefinition getUnderlyingDefinition() { return _underlyingDefinition; } protected CompiledFunctionDefinition getUnderlyingCompiled() { return _underlyingCompiled; } protected FunctionInvoker getUnderlyingInvoker() { return _underlyingInvoker; } protected String getAggregationStyleMissing() { return AGGREGATION_STYLE_MISSING; } protected String getAggregationStyleFull() { return AGGREGATION_STYLE_FULL; } // AbstractFunction @Override public void setUniqueId(final String identifier) { if (getUnderlyingDefinition() instanceof AbstractFunction) { ((AbstractFunction) getUnderlyingDefinition()).setUniqueId(identifier); } super.setUniqueId(identifier); } // FunctionDefinition @Override public void init(final FunctionCompilationContext context) { getUnderlyingDefinition().init(context); } @Override public CompiledFunctionDefinition compile(final FunctionCompilationContext context, final Instant atInstant) { final CompiledFunctionDefinition underlying = getUnderlyingDefinition().compile(context, atInstant); if (underlying == getUnderlyingCompiled()) { s_logger.debug("Compiling underlying on {} gives self", this); return this; } else { s_logger.debug("Creating delegate for compiled underlying on {}", this); return create(underlying); } } @Override public String getShortName() { return getUnderlyingDefinition().getShortName(); } @Override public FunctionParameters getDefaultParameters() { return getUnderlyingDefinition().getDefaultParameters(); } // CompiledFunctionDefinition @Override public FunctionDefinition getFunctionDefinition() { return this; } @Override public ComputationTargetType getTargetType() { return getUnderlyingCompiled().getTargetType(); } @Override public boolean canApplyTo(final FunctionCompilationContext context, final ComputationTarget target) { return getUnderlyingCompiled().canApplyTo(context, target); } @Override public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target) { final Set<ValueSpecification> underlyingResults = getUnderlyingCompiled().getResults(context, target); if (underlyingResults == null) { s_logger.debug("Underlying returned null for target {}", target); return null; } final Set<ValueSpecification> results = Sets.newHashSetWithExpectedSize(underlyingResults.size()); for (final ValueSpecification underlyingResult : underlyingResults) { final ValueProperties underlyingProperties = underlyingResult.getProperties(); final ValueProperties.Builder properties = underlyingProperties.copy(); if (underlyingProperties.getProperties().isEmpty()) { // Got the infinite or nearly infinite property set properties.withAny(ValuePropertyNames.AGGREGATION); results.add(new ValueSpecification(underlyingResult.getValueName(), underlyingResult.getTargetSpecification(), properties.get())); } else { // Got a finite property set; republish with both aggregation modes properties.withoutAny(ValuePropertyNames.AGGREGATION).with(ValuePropertyNames.AGGREGATION, getAggregationStyleFull()); results.add(new ValueSpecification(underlyingResult.getValueName(), underlyingResult.getTargetSpecification(), properties.get())); properties.withoutAny(ValuePropertyNames.AGGREGATION).with(ValuePropertyNames.AGGREGATION, getAggregationStyleMissing()); results.add(new ValueSpecification(underlyingResult.getValueName(), underlyingResult.getTargetSpecification(), properties.get())); } } s_logger.debug("Returning results {}", results); return results; } @Override public Set<ValueRequirement> getRequirements(final FunctionCompilationContext context, final ComputationTarget target, ValueRequirement desiredValue) { // User must have requested our aggregation style final ValueProperties constraints = desiredValue.getConstraints(); if (constraints.getProperties() == null) { // No constraints - assume FULL and make it optional for the inputs desiredValue = new ValueRequirement(desiredValue.getValueName(), desiredValue.getTargetReference(), ValueProperties.with(ValuePropertyNames.AGGREGATION, getAggregationStyleFull()) .withOptional(ValuePropertyNames.AGGREGATION).get()); } else if (constraints.getProperties().isEmpty()) { // Infinite/near-infinite constraints - make aggregation style optional if (!constraints.isOptional(ValuePropertyNames.AGGREGATION)) { desiredValue = new ValueRequirement(desiredValue.getValueName(), desiredValue.getTargetReference(), constraints.copy().withOptional(ValuePropertyNames.AGGREGATION).get()); } } else { final Set<String> aggregationStyle = constraints.getValues(ValuePropertyNames.AGGREGATION); final String full = getAggregationStyleFull(); if ((aggregationStyle == null) || aggregationStyle.isEmpty()) { // No constraint or wild-card - assume FULL and make it optional for the inputs desiredValue = new ValueRequirement(desiredValue.getValueName(), desiredValue.getTargetReference(), constraints.copy().withoutAny(ValuePropertyNames.AGGREGATION) .withOptional(ValuePropertyNames.AGGREGATION).with(ValuePropertyNames.AGGREGATION, full).get()); } else if (aggregationStyle.contains(full)) { // Constraint allows FULL - make it optional for the inputs if ((aggregationStyle.size() != 1) || !constraints.isOptional(ValuePropertyNames.AGGREGATION)) { desiredValue = new ValueRequirement(desiredValue.getValueName(), desiredValue.getTargetReference(), constraints.copy().withoutAny(ValuePropertyNames.AGGREGATION) .withOptional(ValuePropertyNames.AGGREGATION).with(ValuePropertyNames.AGGREGATION, full).get()); } } else { final String missing = getAggregationStyleMissing(); if (aggregationStyle.contains(missing)) { // Constraint allows MISSING - make it optional for the inputs if ((aggregationStyle.size() != 1) || !constraints.isOptional(ValuePropertyNames.AGGREGATION)) { desiredValue = new ValueRequirement(desiredValue.getValueName(), desiredValue.getTargetReference(), constraints.copy().withoutAny(ValuePropertyNames.AGGREGATION) .withOptional(ValuePropertyNames.AGGREGATION).with(ValuePropertyNames.AGGREGATION, missing).get()); } } else { // Unsupported aggregation style return null; } } } final Set<ValueRequirement> requirements = getUnderlyingCompiled().getRequirements(context, target, desiredValue); s_logger.debug("Returning requirements {} for {}", requirements, desiredValue); return requirements; } @Override public boolean canHandleMissingRequirements() { return getUnderlyingCompiled().canHandleMissingRequirements(); } @Override public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target, final Map<ValueSpecification, ValueRequirement> inputs) { final Set<ValueSpecification> underlyingResults = getUnderlyingCompiled().getResults(context, target, inputs); if (underlyingResults == null) { s_logger.debug("Underlying returned null inputs {}", inputs); return null; } final String full = getAggregationStyleFull(); final String missing = getAggregationStyleMissing(); boolean resultFull = false; boolean resultMissing = false; for (ValueRequirement input : inputs.values()) { final Set<String> inputAgg = input.getConstraints().getValues(ValuePropertyNames.AGGREGATION); if (inputAgg != null) { if (inputAgg.contains(full)) { resultFull = true; } if (inputAgg.contains(missing)) { resultMissing = true; } } } if (!resultFull && !resultMissing) { resultFull = true; resultMissing = true; } final Set<ValueSpecification> results = Sets.newHashSetWithExpectedSize(underlyingResults.size() * ((resultFull && resultMissing) ? 2 : 1)); for (final ValueSpecification underlyingResult : underlyingResults) { final ValueProperties properties = underlyingResult.getProperties(); if ((properties.getProperties() != null) && properties.getProperties().isEmpty()) { results.add(underlyingResult); } else { final ValueProperties.Builder builder = properties.copy(); if (resultFull) { builder.withoutAny(ValuePropertyNames.AGGREGATION).with(ValuePropertyNames.AGGREGATION, getAggregationStyleFull()); results.add(new ValueSpecification(underlyingResult.getValueName(), underlyingResult.getTargetSpecification(), builder.get())); } if (resultMissing) { builder.withoutAny(ValuePropertyNames.AGGREGATION).with(ValuePropertyNames.AGGREGATION, getAggregationStyleMissing()); results.add(new ValueSpecification(underlyingResult.getValueName(), underlyingResult.getTargetSpecification(), builder.get())); } } } s_logger.debug("Returning results {} for {}", results, inputs); return results; } @Override public Set<ValueRequirement> getAdditionalRequirements(final FunctionCompilationContext context, final ComputationTarget target, final Set<ValueSpecification> inputs, final Set<ValueSpecification> outputs) { final Set<ValueSpecification> underlyingOutputs = Sets.newHashSetWithExpectedSize(outputs.size()); for (final ValueSpecification output : outputs) { final ValueProperties properties = output.getProperties().withoutAny(ValuePropertyNames.AGGREGATION); underlyingOutputs.add(new ValueSpecification(output.getValueName(), output.getTargetSpecification(), properties)); } return getUnderlyingCompiled().getAdditionalRequirements(context, target, inputs, underlyingOutputs); } @Override public Instant getEarliestInvocationTime() { return getUnderlyingCompiled().getEarliestInvocationTime(); } @Override public Instant getLatestInvocationTime() { return getUnderlyingCompiled().getLatestInvocationTime(); } @Override public FunctionInvoker getFunctionInvoker() { final FunctionInvoker underlying = getUnderlyingCompiled().getFunctionInvoker(); if (underlying == getUnderlyingInvoker()) { return this; } else { return create(underlying); } } // FunctionInvoker private Set<ComputedValue> createExecuteResults(final FunctionInputs inputs, final Set<ComputedValue> underlyingResults) { if (underlyingResults == null) { return Collections.emptySet(); } final Set<ComputedValue> results = Sets.newHashSetWithExpectedSize(underlyingResults.size()); for (final ComputedValue underlyingResult : underlyingResults) { final ValueSpecification resultSpec = underlyingResult.getSpecification(); final ValueProperties.Builder properties = resultSpec.getProperties().copy(); properties.withoutAny(ValuePropertyNames.AGGREGATION).with(ValuePropertyNames.AGGREGATION, getAggregationStyleMissing()); results.add(new ComputedValue(new ValueSpecification(resultSpec.getValueName(), resultSpec.getTargetSpecification(), properties.get()), underlyingResult.getValue())); if (inputs.getMissingValues().isEmpty()) { properties.withoutAny(ValuePropertyNames.AGGREGATION).with(ValuePropertyNames.AGGREGATION, getAggregationStyleFull()); results.add(new ComputedValue(new ValueSpecification(resultSpec.getValueName(), resultSpec.getTargetSpecification(), properties.get()), underlyingResult.getValue())); } } return results; } @Override public Set<ComputedValue> execute(final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target, final Set<ValueRequirement> desiredValues) throws AsynchronousExecution { final Set<ValueRequirement> underlyingDesired = Sets.newHashSetWithExpectedSize(desiredValues.size()); for (final ValueRequirement desiredValue : desiredValues) { final ValueProperties requirementConstraints = desiredValue.getConstraints().withoutAny(ValuePropertyNames.AGGREGATION); underlyingDesired.add(new ValueRequirement(desiredValue.getValueName(), desiredValue.getTargetReference(), requirementConstraints)); } try { return createExecuteResults(inputs, getUnderlyingInvoker().execute(executionContext, inputs, target, underlyingDesired)); } catch (final AsynchronousExecution e) { final AsynchronousOperation<Set<ComputedValue>> async = AsynchronousOperation.createSet(); e.setResultListener(new ResultListener<Set<ComputedValue>>() { @Override public void operationComplete(final AsynchronousResult<Set<ComputedValue>> result) { try { async.getCallback().setResult(createExecuteResults(inputs, result.getResult())); } catch (final RuntimeException e) { async.getCallback().setException(e); } } }); return async.getResult(); } } @Override public boolean canHandleMissingInputs() { return true; } }