/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.component.model.spi; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import de.rcenvironment.core.component.api.ComponentException; import de.rcenvironment.core.component.api.LoopComponentConstants; import de.rcenvironment.core.component.api.LoopComponentConstants.LoopBehaviorInCaseOfFailure; import de.rcenvironment.core.component.execution.api.ComponentContext; import de.rcenvironment.core.component.execution.api.ComponentLog; import de.rcenvironment.core.datamodel.api.DataType; import de.rcenvironment.core.datamodel.api.EndpointCharacter; import de.rcenvironment.core.datamodel.api.TypedDatum; import de.rcenvironment.core.datamodel.api.TypedDatumFactory; import de.rcenvironment.core.datamodel.api.TypedDatumService; import de.rcenvironment.core.datamodel.types.api.NotAValueTD; import de.rcenvironment.core.utils.common.StringUtils; /** * The {@link AbstractLoopComponent} is an abstract class that must be used for components that should be able to control a loop. * * @author Doreen Seider * @author Sascha Zur */ public abstract class AbstractLoopComponent extends DefaultComponent { private static final String FAIL_WORKFLOW_MESSAGE = "-> fail workflow (as defined by behavior declaration in configuration tab 'Fault Tolerance')"; private static final String RECEIVED_NAV_VALUES = "Received value(s) of type 'Not a value' (explicitly sent by a component in the loop) "; private static final String COMPONENT_FAILED = "A component in the loop failed "; protected ComponentContext componentContext; protected ComponentLog componentLog; protected TypedDatumFactory typedDatumFactory; protected LoopBehaviorInCaseOfFailure loopBehaviorInCaseOfNAV; protected boolean loopFailureRequested = false; private boolean anyRunFailedNAV = false; private int maximumReruns; private int rerunCount = 0; private Map<String, TypedDatum> outputValuesSentInLastLoop = new HashMap<>(); @Override public void setComponentContext(ComponentContext componentContext) { this.componentContext = componentContext; this.componentLog = componentContext.getLog(); } @Override public void start() throws ComponentException { typedDatumFactory = componentContext.getService(TypedDatumService.class).getFactory(); loopBehaviorInCaseOfNAV = LoopBehaviorInCaseOfFailure.fromString( componentContext.getConfigurationValue(LoopComponentConstants.CONFIG_KEY_LOOP_FAULT_TOLERANCE_NAV)); switch (loopBehaviorInCaseOfNAV) { case RerunAndFail: maximumReruns = getRerunCount(LoopComponentConstants.CONFIG_KEY_MAX_RERUN_BEFORE_FAIL_NAV); break; case RerunAndDiscard: maximumReruns = getRerunCount(LoopComponentConstants.CONFIG_KEY_MAX_RERUN_BEFORE_DISCARD_NAV); break; default: // nothing to do break; } startComponentSpecific(); if (treatStartAsComponentRun()) { sendLoopDoneValuesIfDone(); } } private int getRerunCount(String configKey) throws ComponentException { String rerunConfigValue = componentContext.getConfigurationValue(configKey); try { return Integer.valueOf(rerunConfigValue); } catch (NumberFormatException e) { if (rerunConfigValue == null || rerunConfigValue.isEmpty()) { throw new ComponentException("No number of reruns given"); } else { throw new ComponentException(StringUtils.format("Given number of reruns is invalid: %s", rerunConfigValue)); } } } @Override public void processInputs() throws ComponentException { if (hasNaVInputValues(componentContext.getInputsWithDatum())) { boolean failureCausedByCompFailure = hasNaVInputValuesCausedByComponentFailure(componentContext.getInputsWithDatum()); if (!failureCausedByCompFailure) { anyRunFailedNAV = true; } if (handleFaultTolerance(failureCausedByCompFailure)) { return; } } outputValuesSentInLastLoop.clear(); rerunCount = 0; processInputsComponentSpecific(); if (!isReset()) { sendLoopDoneValuesIfDone(); if (isFinallyDone()) { componentContext.closeAllOutputs(); } else { forwardValues(); } } } protected void writeOutput(String outputName, TypedDatum value) { if (componentContext.getOutputCharacter(outputName).equals(EndpointCharacter.SAME_LOOP)) { outputValuesSentInLastLoop.put(outputName, value); } componentContext.writeOutput(outputName, value); } protected boolean hasNaVInputValues(Set<String> inputsWithDatum) { return hasNaVInputValues(inputsWithDatum, false); } protected boolean hasNaVInputValuesCausedByComponentFailure(Set<String> inputsWithDatum) { return hasNaVInputValues(inputsWithDatum, true); } protected boolean hasNaVInputValues(Set<String> inputsWithDatum, boolean causedByCompFailureOnly) { for (String inputName : inputsWithDatum) { if (componentContext.getInputCharacter(inputName).equals(EndpointCharacter.SAME_LOOP)) { TypedDatum typedDatum = componentContext.readInput(inputName); if (typedDatum.getDataType().equals(DataType.NotAValue)) { if (!causedByCompFailureOnly) { return true; } else if (((NotAValueTD) typedDatum).getCause().equals(NotAValueTD.Cause.Failure)) { return true; } } } } return false; } private boolean handleFaultTolerance(boolean failureCausedByCompFailure) throws ComponentException { boolean handled = false; if (failureCausedByCompFailure) { // Currently, 'discard' is the only option that is handled by drivers in case of component failures. In case 'fail' was chosen, // the workflow engine will request that information and will directly fail the responsible component handled = discardLoopRun(); } else { switch (loopBehaviorInCaseOfNAV) { case Fail: handled = handleFailure(new ComponentException(getErrorMessage(failureCausedByCompFailure) + FAIL_WORKFLOW_MESSAGE)); break; case Discard: handled = discardLoopRun(); break; case RerunAndFail: handled = rerunOrFailLoopRun(); break; case RerunAndDiscard: handled = rerunOrDiscardLoopRun(handled); break; default: // should not happen break; } } return handled; } private boolean discardLoopRun() throws ComponentException { boolean handled; String inputName1 = guardAgainstNaVValueAtForwardingInputs(componentContext.getInputsWithDatum()); if (inputName1 != null) { NotAValueTD readInput = (NotAValueTD) componentContext.readInput(inputName1); switch (readInput.getCause()) { case InvalidInputs: handled = handleFailure(new ComponentException(StringUtils.format("Received value of type 'Not a value' at input" + " '%s' that is not allowed to be forwarded; most likely reason: sent by a component of a fault-tolerant loop", inputName1))); break; case Failure: default: handled = handleFailure(new ComponentException( StringUtils.format("Failure in fault-tolerant loop but loop run cannot be discarded as no reasonable" + " value to forward exists for '%s' now", inputName1))); break; } } else { componentLog.componentInfo(RECEIVED_NAV_VALUES + "-> discard evaluation loop run (as defined by behavior declaration in configuration tab 'Fault Tolerance')"); handled = false; } return handled; } private boolean rerunOrFailLoopRun() throws ComponentException { boolean handled; if (rerunCount >= maximumReruns) { handled = handleFailure(new ComponentException(StringUtils.format(RECEIVED_NAV_VALUES + " and maximum number of reruns execeeded (%d) " + FAIL_WORKFLOW_MESSAGE, maximumReruns))); } else { rerunLoop(); handled = true; } return handled; } private boolean rerunOrDiscardLoopRun(boolean handled) throws ComponentException { if (rerunCount >= maximumReruns) { String inputName2 = guardAgainstNaVValueAtForwardingInputs(componentContext.getInputsWithDatum()); if (inputName2 != null) { // TODO adapt error message if new data type beside not-a-value is introduced to indicated component crashes handled = handleFailure(new ComponentException(StringUtils.format("Received value of type 'Not a value' at" + " input '%s' that is not allowed to be forwarded; most likely reason: failure in a fault-tolerant" + " loop so that no reasonable value to forward was provided", inputName2))); } } else { rerunLoop(); handled = true; } return handled; } private String getErrorMessage(boolean failureCausedByComponentCrash) { if (failureCausedByComponentCrash) { return COMPONENT_FAILED; } else { return RECEIVED_NAV_VALUES; } } private boolean handleFailure(ComponentException e) throws ComponentException { if (isFailLoopOnly()) { loopFailureRequested = true; return false; } else { throw e; } } private boolean isFailLoopOnly() { return Boolean.valueOf(componentContext.getConfigurationValue(LoopComponentConstants.CONFIG_KEY_FAIL_LOOP_ONLY_NAV)); } private boolean isFinallyFail() { return Boolean.valueOf(componentContext.getConfigurationValue(LoopComponentConstants.CONFIG_KEY_FINALLY_FAIL_IF_DISCARDED_NAV)); } private void rerunLoop() { rerunCount++; componentLog.componentInfo(StringUtils.format(RECEIVED_NAV_VALUES + "-> re-run evaluation loop (rerun count: %d) " + "(as defined by behavior declaration in configuration tab 'Fault Tolerance')", rerunCount)); for (String outputName : outputValuesSentInLastLoop.keySet()) { componentContext.writeOutput(outputName, outputValuesSentInLastLoop.get(outputName)); } } private String guardAgainstNaVValueAtForwardingInputs(Set<String> inputsWithDatum) throws ComponentException { for (String inputName : inputsWithDatum) { if (componentContext.getDynamicInputIdentifier(inputName).equals(LoopComponentConstants.ENDPOINT_ID_TO_FORWARD)) { TypedDatum typedDatum = componentContext.readInput(inputName); if (typedDatum.getDataType().equals(DataType.NotAValue)) { return inputName; } } } return null; } @Override public void reset() throws ComponentException { if (loopFailureRequested) { resetComponentSpecific(); loopFailureRequested = false; componentLog.componentInfo(RECEIVED_NAV_VALUES + "-> forward to outer loop (as defined by behavior declaration in configuration tab 'Fault Tolerance')"); writeNAVValueToOuterLoop(); } else { finishLoopComponentSpecific(false); sendLoopDoneValue(true); resetComponentSpecific(); } } private void writeNAVValueToOuterLoop() { for (String outputName : getOuterLoopOutputs()) { componentContext.writeOutput(outputName, typedDatumFactory.createNotAValue(NotAValueTD.Cause.InvalidInputs)); } } private Set<String> getOuterLoopOutputs() { Set<String> outerLoopOutputs = new HashSet<>(); for (String outputName : componentContext.getOutputs()) { if (componentContext.getOutputCharacter(outputName).equals(EndpointCharacter.OUTER_LOOP)) { outerLoopOutputs.add(outputName); } } return outerLoopOutputs; } protected void addOutputValueSentInSelfLoop(String outputName, TypedDatum value) { outputValuesSentInLastLoop.put(outputName, value); } /** * Check if the component has forwarding start values to wait for. * * @return true, if there are any. */ protected boolean hasForwardingStartInputs() { Set<String> inputs = componentContext.getInputs(); for (String input : inputs) { if (componentContext.isDynamicInput(input) && componentContext.getDynamicInputIdentifier(input).equals(LoopComponentConstants.ENDPOINT_ID_START_TO_FORWARD)) { return true; } } return false; } private void forwardValues() { Set<String> inputs = componentContext.getInputsWithDatum(); for (String input : inputs) { if (!componentContext.isStaticInput(input) && (componentContext.getDynamicInputIdentifier(input).equals(LoopComponentConstants.ENDPOINT_ID_TO_FORWARD) || componentContext.getDynamicInputIdentifier(input).equals(LoopComponentConstants.ENDPOINT_ID_START_TO_FORWARD))) { String output; if (input.endsWith(LoopComponentConstants.ENDPOINT_STARTVALUE_SUFFIX)) { output = input.substring(0, input.indexOf(LoopComponentConstants.ENDPOINT_STARTVALUE_SUFFIX)); } else { output = input; } writeOutput(output, componentContext.readInput(input)); } } } private void sendLoopDoneValuesIfDone() throws ComponentException { if (isDone() || isFinallyDone()) { if (anyRunFailedNAV && isFinallyFail()) { throw new ComponentException("Evaluation loop terminated and at least one evaluation loop run was discarded " + "- fail (as defined by behavior declaration in configuration tab 'Fault Tolerance')"); } } if (isDone()) { sendLoopDoneValue(true); } } private void sendLoopDoneValue(boolean done) { componentContext.writeOutput(LoopComponentConstants.ENDPOINT_NAME_LOOP_DONE, typedDatumFactory.createBoolean(done)); } protected boolean isReset() { return false; } protected abstract void finishLoopComponentSpecific(boolean outerLoopFinished) throws ComponentException; /** * {@link DefaultComponent#reset()} of components implementing this abstract class. */ protected void resetComponentSpecific() throws ComponentException {} /** * {@link DefaultComponent#processInputs()} of components implementing this abstract class. */ protected abstract void processInputsComponentSpecific() throws ComponentException; /** * {@link DefaultComponent#start()} of components implementing this abstract class. */ protected abstract void startComponentSpecific() throws ComponentException; /** * return <code>true</code> if the loop is terminated and thus, the loop component done, otherwise <code>false</code>. */ protected abstract boolean isFinallyDone(); /** * return <code>true</code> if the loop is terminated and thus, the loop component done, otherwise <code>false</code>. */ protected abstract boolean isDone(); }