/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.component.execution.internal;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import de.rcenvironment.core.component.datamanagement.api.ComponentHistoryDataItem;
import de.rcenvironment.core.component.execution.api.Component;
import de.rcenvironment.core.component.execution.api.ComponentContext;
import de.rcenvironment.core.component.execution.api.ComponentExecutionException;
import de.rcenvironment.core.component.execution.api.ComponentState;
import de.rcenvironment.core.component.execution.api.ConsoleRow;
import de.rcenvironment.core.component.execution.api.ConsoleRow.Type;
import de.rcenvironment.core.component.execution.api.WorkflowGraphHop;
import de.rcenvironment.core.component.model.endpoint.api.EndpointDatum;
import de.rcenvironment.core.component.model.endpoint.api.EndpointDescription;
import de.rcenvironment.core.datamodel.api.DataType;
import de.rcenvironment.core.datamodel.api.DataTypeException;
import de.rcenvironment.core.datamodel.api.TypedDatum;
import de.rcenvironment.core.datamodel.api.TypedDatumConverter;
import de.rcenvironment.core.datamodel.api.TypedDatumService;
import de.rcenvironment.core.datamodel.types.api.NotAValueTD;
import de.rcenvironment.core.utils.common.StringUtils;
/**
* Processes method callbacks from {@link ComponentContext} performed by the {@link Component} during execution.
*
* @author Doreen Seider
*/
public class ComponentContextBridge {
private static final String NOT_CONVERTIBLE_MESSAGE = "Datum of type '%s' is not convertible to data type '%s' expected by input '%s'";
private static final Log LOG = LogFactory.getLog(ComponentContextBridge.class);
private static TypedDatumService typedDatumService;
private final Map<String, DataType> inputDataTypes = new HashMap<>();
private ComponentExecutionRelatedInstances compExeRelatedInstances;
private Map<String, EndpointDatum> endpointDatumsForExecution = Collections.synchronizedMap(new HashMap<String, EndpointDatum>());
private Map<String, Boolean> closedOutputs = Collections.synchronizedMap(new HashMap<String, Boolean>());
private List<OutputHolder> outputsOnHold = Collections.synchronizedList(new ArrayList<OutputHolder>());
private final Object historyDataLock = new Object();
@Deprecated
public ComponentContextBridge() {}
public ComponentContextBridge(ComponentExecutionRelatedInstances compExeRelatedInstances) {
this.compExeRelatedInstances = compExeRelatedInstances;
for (EndpointDescription epDesc : compExeRelatedInstances.compExeCtx.getComponentDescription().getInputDescriptionsManager()
.getEndpointDescriptions()) {
inputDataTypes.put(epDesc.getName(), epDesc.getDataType());
}
}
/**
* Writes given {@link TypedDatum} to the output. If the output is connected to inputs, the {@link TypedDatum} will be delivered
* to all inputs.
*
* @param outputName name of the output to write
* @param datumToSend {@link TypedDatum} to be sent
*/
public synchronized void writeOutput(String outputName, TypedDatum datumToSend) {
EndpointDescription endpointDescription =
compExeRelatedInstances.compExeCtx.getComponentDescription().getOutputDescriptionsManager()
.getEndpointDescription(outputName);
Long outputDmId = null;
try {
outputDmId = compExeRelatedInstances.compExeStorageBridge.addOutput(outputName,
typedDatumService.getSerializer().serialize(datumToSend));
} catch (ComponentExecutionException e) {
throw new RuntimeException(e);
}
if (endpointDescription.isConnected()) {
validateOutputDataType(outputName, endpointDescription.getDataType(), datumToSend);
if (ComponentExecutionUtils.isVerificationRequired(compExeRelatedInstances.compExeCtx.getComponentDescription()
.getConfigurationDescription().getComponentConfigurationDefinition())) {
holdOutput(outputName, datumToSend, outputDmId);
} else {
sendOutput(outputName, datumToSend, outputDmId);
}
}
closedOutputs.put(outputName, false);
}
private void sendOutput(String outputName, TypedDatum datumToSend, Long outputDmId) {
compExeRelatedInstances.typedDatumToOutputWriter.writeTypedDatumToOutput(outputName, datumToSend, outputDmId);
if (datumToSend.getDataType().equals(DataType.NotAValue)) {
compExeRelatedInstances.compExeScheduler.addNotAValueDatumSent(((NotAValueTD) datumToSend).getIdentifier());
}
}
protected void holdOutput(String outputName, TypedDatum datumToSend, Long outputDmId) {
outputsOnHold.add(new OutputHolder(outputName, datumToSend, outputDmId));
}
protected void flushOutputs() {
for (OutputHolder outputHolder : outputsOnHold) {
sendOutput(outputHolder.outputName, outputHolder.datumToSend, outputHolder.outputDmId);
}
}
protected void setEndpointDatumsForExecution(Map<String, EndpointDatum> endpointDatumsForExecution) throws ComponentExecutionException {
this.endpointDatumsForExecution.clear();
this.endpointDatumsForExecution.putAll(endpointDatumsForExecution);
validateEndpointDatumsForExecution();
}
protected Map<String, EndpointDatum> getEndpointDatumsForExecution() {
return Collections.unmodifiableMap(endpointDatumsForExecution);
}
private void validateIfNestedLoopComponent(String outputName) {
if (!compExeRelatedInstances.isNestedLoopDriver) {
throw new RuntimeException(getLogMessagesPrefix() + StringUtils.format(
"Received reset datum at output '%s' for a non nested loop component. "
+ "Reset datums are only allowed to send by nested loop components.",
outputName));
}
}
private void validateEndpointDatumsForExecution() throws ComponentExecutionException {
for (EndpointDatum endpointDatum : endpointDatumsForExecution.values()) {
DataType inputDataType = inputDataTypes.get(endpointDatum.getInputName());
if (endpointDatum.getValue().getDataType() != inputDataType && endpointDatum.getValue().getDataType() != DataType.NotAValue) {
if (!typedDatumService.getConverter().isConvertibleTo(endpointDatum.getValue(), inputDataType)) {
throw new ComponentExecutionException(
StringUtils.format(NOT_CONVERTIBLE_MESSAGE,
endpointDatum.getValue().getDataType(), inputDataType, endpointDatum.getInputName()));
}
}
}
}
private void validateOutputDataType(String outputName, DataType dataType, TypedDatum datumToSent) {
if (!datumToSent.getDataType().equals(DataType.NotAValue) && !datumToSent.getDataType().equals(DataType.Internal)) {
if (datumToSent.getDataType() != dataType) {
TypedDatumConverter converter = typedDatumService.getConverter();
if (converter.isConvertibleTo(datumToSent, dataType)) {
try {
datumToSent = converter.castOrConvert(datumToSent, dataType);
} catch (DataTypeException e) {
// should not be reached because of isConvertibleTo check before
throw new RuntimeException(getLogMessagesPrefix() + StringUtils.format("Failed to convert "
+ "the value for output '" + outputName + "' from type " + datumToSent.getDataType()
+ " to required data type " + dataType));
}
} else {
throw new RuntimeException(getLogMessagesPrefix() + StringUtils.format("Value for output '" + outputName
+ "' has invalid data type. Output requires " + dataType + " or a convertible one, but it is of type "
+ datumToSent.getDataType()));
}
}
}
}
/**
* @return all inputs with a value. I.e. {@link #readInput(String)} will be return a {@link TypedDatum}.
*/
public Set<String> getInputsWithDatum() {
return new HashSet<>(endpointDatumsForExecution.keySet());
}
/**
* Reads input with the given name.
*
* @param inputName name of the input to read
* @return {@link TypedDatum} currently available at the input
* @throws NoSuchElementException if there is no {@link TypedDatum} available
*/
public TypedDatum readInput(String inputName) {
if (endpointDatumsForExecution.containsKey(inputName)) {
final EndpointDatum endpointDatum = endpointDatumsForExecution.get(inputName);
DataType inputDataType = inputDataTypes.get(endpointDatum.getInputName());
if (endpointDatum.getValue().getDataType() == inputDataType || endpointDatum.getValue().getDataType() == DataType.NotAValue) {
return endpointDatum.getValue();
} else {
try {
return typedDatumService.getConverter().castOrConvert(endpointDatum.getValue(), inputDataType);
} catch (DataTypeException e) { // should not happen due to early validation by validateEndpointDatumsForExecution()
throw new RuntimeException(
StringUtils.format(NOT_CONVERTIBLE_MESSAGE, endpointDatum.getValue().getDataType(), inputDataType, inputName));
}
}
} else {
throw new NoSuchElementException(getLogMessagesPrefix() + StringUtils.format("No datum at input '%s'", inputName));
}
}
/**
* Prints given line to the workflow console. It is temporarily also used to send timeline events.
*
* @param line line to print
* @param consoleRowType type of the console row. Must be one of {@link ConsoleRow.Type.STDOUT} or
* {@link ConsoleRow.Type.STDERR} are allowed
*/
public void printConsoleRow(String line, Type consoleRowType) {
if (consoleRowType.equals(Type.LIFE_CYCLE_EVENT)) {
compExeRelatedInstances.consoleRowsSender.sendTimelineEventAsConsoleRow(consoleRowType, line);
} else {
compExeRelatedInstances.consoleRowsSender.sendLogMessageAsConsoleRow(consoleRowType, line,
compExeRelatedInstances.compExeRelatedStates.executionCount.get());
}
}
/**
* Closes all outputs.
*/
public void closeAllOutputs() {
for (EndpointDescription output : compExeRelatedInstances.compExeCtx.getComponentDescription()
.getOutputDescriptionsManager().getEndpointDescriptions()) {
closeOutput(output.getName());
}
}
/**
* Closes an output with given name.
*
* @param outputName name of output to close.
*/
public synchronized void closeOutput(String outputName) {
if (!closedOutputs.containsKey(outputName) || !closedOutputs.get(outputName)) {
closedOutputs.put(outputName, true);
compExeRelatedInstances.typedDatumToOutputWriter.writeTypedDatumToOutput(outputName,
new InternalTDImpl(InternalTDImpl.InternalTDType.WorkflowFinish));
} else {
printConsoleRow(StringUtils.format("Output '%s' already closed. "
+ "Ignored further closing request.", outputName), ConsoleRow.Type.COMPONENT_WARN);
}
}
/**
* @param outputName name of output
* @return <code>true</code> if output is closed, otherwise <code>false</code>
*/
public synchronized boolean isOutputClosed(String outputName) {
return closedOutputs.containsKey(outputName) && closedOutputs.get(outputName);
}
/**
* Resets an output with given name.
*
* @param outputName name of output
*/
public void resetOutput(String outputName) {
validateIfNestedLoopComponent(outputName);
writeResetOutputData(outputName);
}
private void writeResetOutputData(String outputName) {
try {
for (Queue<WorkflowGraphHop> hops : compExeRelatedInstances.compExeCtx.getWorkflowGraph()
.getHopsToTraverseWhenResetting(compExeRelatedInstances.compExeCtx.getExecutionIdentifier())
.get(outputName)) {
WorkflowGraphHop firstHop = hops.poll();
InternalTDImpl resetDatum = new InternalTDImpl(InternalTDImpl.InternalTDType.NestedLoopReset, hops);
compExeRelatedInstances.compExeScheduler.addResetDataIdSent(resetDatum.getIdentifier());
compExeRelatedInstances.typedDatumToOutputWriter.writeTypedDatumToOutputConsideringOnlyCertainInputs(outputName, resetDatum,
firstHop.getTargetExecutionIdentifier(), firstHop.getTargetInputName());
}
} catch (ComponentExecutionException e) {
throw new RuntimeException("Failed to reset the loop. Double-check your loop. Data between loops must "
+ "only be exchanged via evaluation driver components via appropriate inputs and outputs (self, outer, inner)", e);
}
}
/**
* @return current execution count of the component. Count starts with 1. It is 1 within {@link Component#start(ComponentContext)} and
* is 1 within {@link Component#processInputs()} if {@link Component#start(ComponentContext)} returns <code>false</code> or 2
* otherwise.
*/
public int getExecutionCount() {
return compExeRelatedInstances.compExeRelatedStates.executionCount.get();
}
/**
* Writes intermediate history data. Each new intermediate history data will overwrite a previous one.
*
* @param compHistoryDataItem {@link ComponentHistoryDataItem} to write
*/
public void writeIntermediateHistoryData(ComponentHistoryDataItem compHistoryDataItem) {
if (writeHistoryDataItem(compHistoryDataItem)) {
compExeRelatedInstances.compExeRelatedStates.intermediateHistoryDataWritten.set(true);
}
}
/**
* Writes final history data. It will overwrite any intermediate ones.
*
* @param compHistoryDataItem {@link ComponentHistoryDataItem} to write
*/
public void writeFinalHistoryDataItem(ComponentHistoryDataItem compHistoryDataItem) {
synchronized (historyDataLock) {
if (writeHistoryDataItem(compHistoryDataItem)) {
compExeRelatedInstances.compExeRelatedStates.finalHistoryDataItemWritten.set(true);
}
}
}
/**
* @return data management id of latest component execution
*/
public Long getComponentExecutionDataManagementId() {
ComponentState compState = compExeRelatedInstances.compStateMachine.getState();
if ((compState.equals(ComponentState.STARTING) || compState.equals(ComponentState.PROCESSING_INPUTS))
&& compExeRelatedInstances.compExeRelatedStates.isComponentCancelled.get()) {
return null;
}
return compExeRelatedInstances.compExeStorageBridge.getComponentExecutionDataManagementId();
}
private boolean writeHistoryDataItem(ComponentHistoryDataItem componentHistoryDataItem) {
if (componentHistoryDataItem == null) {
printConsoleRow("Failed to store additional workflow data, because data item was 'null'"
+ " which is a developer error", Type.COMPONENT_ERROR);
LOG.error(StringUtils.format("Failed to store history data item for component '%s' (%s) of workflow '%s' (%s)"
+ " because it was null", compExeRelatedInstances.compExeCtx.getInstanceName(),
compExeRelatedInstances.compExeCtx.getExecutionIdentifier(),
compExeRelatedInstances.compExeCtx.getWorkflowInstanceName(),
compExeRelatedInstances.compExeCtx.getWorkflowExecutionIdentifier()));
return false;
}
try {
compExeRelatedInstances.compExeStorageBridge.setOrUpdateHistoryDataItem(componentHistoryDataItem.serialize(
typedDatumService.getSerializer()));
return true;
} catch (IOException | ComponentExecutionException e) {
throw new RuntimeException(e);
}
}
private String getLogMessagesPrefix() {
return StringUtils.format("Component '%s' (%s) of workflow '%s' (%s): ",
compExeRelatedInstances.compExeCtx.getInstanceName(),
compExeRelatedInstances.compExeCtx.getExecutionIdentifier(),
compExeRelatedInstances.compExeCtx.getWorkflowInstanceName(),
compExeRelatedInstances.compExeCtx.getWorkflowExecutionIdentifier());
}
/**
* Holder class for outputs that are on hold because verification needs to be requested first.
*
* @author Doreen Seider
*/
class OutputHolder {
private final String outputName;
private final TypedDatum datumToSend;
private final Long outputDmId;
OutputHolder(String outputName, TypedDatum datumToSend, Long outputDmId) {
this.outputName = outputName;
this.datumToSend = datumToSend;
this.outputDmId = outputDmId;
}
}
protected void bindTypedDatumService(TypedDatumService newService) {
ComponentContextBridge.typedDatumService = newService;
}
}