/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.gui.datamanagement.browser.spi;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import de.rcenvironment.core.component.datamanagement.api.CommonComponentHistoryDataItem;
import de.rcenvironment.core.component.datamanagement.api.ComponentDataManagementService;
import de.rcenvironment.core.component.datamanagement.api.DefaultComponentHistoryDataItem;
import de.rcenvironment.core.component.datamanagement.api.EndpointHistoryDataItem;
import de.rcenvironment.core.datamanagement.commons.MetaDataKeys;
import de.rcenvironment.core.datamodel.api.DataType;
import de.rcenvironment.core.datamodel.api.DataTypeException;
import de.rcenvironment.core.datamodel.api.EndpointType;
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.DirectoryReferenceTD;
import de.rcenvironment.core.datamodel.types.api.FileReferenceTD;
import de.rcenvironment.core.datamodel.types.api.MatrixTD;
import de.rcenvironment.core.datamodel.types.api.NotAValueTD;
import de.rcenvironment.core.datamodel.types.api.SmallTableTD;
import de.rcenvironment.core.datamodel.types.api.VectorTD;
import de.rcenvironment.core.gui.datamanagement.browser.Activator;
import de.rcenvironment.core.utils.common.JsonUtils;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.core.utils.incubator.ServiceRegistry;
import de.rcenvironment.core.utils.incubator.ServiceRegistryAccess;
/**
* Provides methods to build subtrees for common component history data, such as inputs, output, or log files.
*
* @author Doreen Seider
* @author Oliver Seebach
*/
public final class CommonHistoryDataItemSubtreeBuilderUtils {
/**
* short text seperator between content and title.
*/
public static final String COLON = ": ";
/**
* max length of short text content.
*/
public static final int MAX_LABEL_LENGTH = 30;
private static final String STRING_CONVERSION_INFORMATION =
"{\"guiName\":\"Converted from data type %s\", \"value\":\"%s\"}";
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 int MAX_NON_PERSISTENT_ENTRIES = 1000;
private static final String LEAF_TEXT_FORMAT = "%s: %s";
private static final List<String> META_DATA_KEYS_TO_HIDE = new ArrayList<String>(Arrays.asList(
// Set all meta data keys that should not be displayed in the workflow data browser.
MetaDataKeys.DATA_TYPE));
private static Log logger = LogFactory.getLog(CommonHistoryDataItemSubtreeBuilderUtils.class);
private static ObjectMapper mapper = JsonUtils.getDefaultObjectMapper();
private CommonHistoryDataItemSubtreeBuilderUtils() {};
/**
* Builds subtrees for common component history data: inputs, output, and log files.
*
* @param dataItem data point with common history data
* @param parent parent node
*/
public static void buildDefaultHistoryDataItemSubtrees(DefaultComponentHistoryDataItem dataItem, DMBrowserNode parent) {
buildInputsSubstree(dataItem, parent);
buildOutputsSubtree(dataItem, parent);
}
/**
* Builds subtrees for common component history data: inputs, output, and log files.
*
* @param dataItem data point with common history data
* @param parent parent node
*/
public static void buildCommonHistoryDataItemSubtrees(CommonComponentHistoryDataItem dataItem, DMBrowserNode parent) {
buildDefaultHistoryDataItemSubtrees(dataItem, parent);
buildExitCodeNode(dataItem, parent);
}
/**
* Builds input subtree for common component history data.
*
* @param dataItem data point with common history data
* @param parent parent node
*/
public static void buildInputsSubstree(DefaultComponentHistoryDataItem dataItem, DMBrowserNode parent) {
if (dataItem.getInputs().size() > 0) {
DMBrowserNode inputNode = DMBrowserNode.addNewChildNode("Inputs", DMBrowserNodeType.Input, parent);
List<String> inputKeyList = sortKeys(dataItem.getInputs().keySet());
for (String name : inputKeyList) {
Deque<EndpointHistoryDataItem> inputsDeque = dataItem.getInputs().get(name);
// for one value per endpoint at one step
if (inputsDeque.size() == 1) {
EndpointHistoryDataItem currentInput = inputsDeque.pop();
handleDataItem(currentInput, name, inputNode, parent.getDataReferenceId(), EndpointType.INPUT,
dataItem.getMetaDataForInput(name));
// for more than one value per endpoint at one step: endpoint name as child node, values as leaf nodes
} else if (inputsDeque.size() > 1) {
DMBrowserNodeType type = getDMBrowserNodeTypeByDataType(inputsDeque.peekFirst().getValue().getDataType());
DMBrowserNode inputNameNode = DMBrowserNode.addNewChildNode(name, type, inputNode);
for (EndpointHistoryDataItem item : inputsDeque) {
handleDataItem(item, name, inputNameNode, parent.getDataReferenceId(), EndpointType.INPUT,
dataItem.getMetaDataForInput(name));
}
}
}
}
}
/**
* Builds outputs subtree for common component history data.
*
* @param dataItem data point with common history data
* @param parent parent node
*/
public static void buildOutputsSubtree(DefaultComponentHistoryDataItem dataItem, DMBrowserNode parent) {
if (dataItem.getOutputs().size() > 0) {
DMBrowserNode outputNode = DMBrowserNode.addNewChildNode("Outputs", DMBrowserNodeType.Output, parent);
List<String> outputKeyList = sortKeys(dataItem.getOutputs().keySet());
for (String name : outputKeyList) {
Deque<EndpointHistoryDataItem> outputsDeque = dataItem.getOutputs().get(name);
// for one value per endpoint at one step
if (outputsDeque.size() == 1) {
EndpointHistoryDataItem currentOutput = outputsDeque.pop();
handleDataItem(currentOutput, name, outputNode, parent.getDataReferenceId(), EndpointType.OUTPUT,
dataItem.getMetaDataForOutput(name));
} else if (outputsDeque.size() > 1) {
// for more than one value per endpoint at one step: endpoint name as child node, values as leaf nodes
DMBrowserNodeType type = getDMBrowserNodeTypeByDataType(outputsDeque.peekFirst().getValue().getDataType());
DMBrowserNode outputNameNode = DMBrowserNode.addNewChildNode(name, type, outputNode);
for (EndpointHistoryDataItem item : outputsDeque) {
handleDataItem(item, name, outputNameNode, parent.getDataReferenceId(), EndpointType.OUTPUT,
dataItem.getMetaDataForOutput(name));
}
}
}
}
}
/**
* Builds logs subtree for common component history data.
*
* @param dataItem data point with common history data
* @param parent parent node
*/
public static void buildExitCodeNode(CommonComponentHistoryDataItem dataItem, DMBrowserNode parent) {
if (dataItem.getExitCode() != null) {
DMBrowserNode executionLogNode = null;
for (DMBrowserNode node : parent.getChildren()) {
if (node.getTitle().equals(DMBrowserNodeConstants.NODE_NAME_EXECUTION_LOG)) {
executionLogNode = node;
break;
}
}
if (executionLogNode == null) {
executionLogNode = DMBrowserNode.addNewChildNode(DMBrowserNodeConstants.NODE_NAME_EXECUTION_LOG,
DMBrowserNodeType.LogFolder, parent);
}
if (dataItem.getExitCode() != null) {
DMBrowserNode.addNewLeafNode(StringUtils.format("%s: %d", "Exit code", dataItem.getExitCode()),
DMBrowserNodeType.InformationText, executionLogNode);
}
}
}
private static DMBrowserNodeType getDMBrowserNodeTypeByDataType(DataType dataType) {
DMBrowserNodeType type = null;
if (dataType == DataType.ShortText) {
type = DMBrowserNodeType.ShortText;
} else if (dataType == DataType.Boolean) {
type = DMBrowserNodeType.Boolean;
} else if (dataType == DataType.Integer) {
type = DMBrowserNodeType.Integer;
} else if (dataType == DataType.Float) {
type = DMBrowserNodeType.Float;
} else if (dataType == DataType.Vector) {
type = DMBrowserNodeType.Vector;
} else if (dataType == DataType.SmallTable) {
type = DMBrowserNodeType.SmallTable;
} else if (dataType == DataType.NotAValue) {
type = DMBrowserNodeType.Indefinite;
} else if (dataType == DataType.FileReference) {
type = DMBrowserNodeType.DMFileResource;
} else if (dataType == DataType.DirectoryReference) {
type = DMBrowserNodeType.DMDirectoryReference;
} else if (dataType == DataType.Matrix) {
type = DMBrowserNodeType.Matrix;
} else {
// use information type as fallback
type = DMBrowserNodeType.InformationText;
}
return type;
}
private static List<String> sortKeys(Set<String> unsortedKeys) {
List<String> sortedKeys = new ArrayList<>();
sortedKeys.addAll(unsortedKeys);
Collections.sort(sortedKeys);
return sortedKeys;
}
private static void addFileReference(EndpointHistoryDataItem item, DMBrowserNode node) {
if (item.getValue() instanceof FileReferenceTD) {
FileReferenceTD fileReference = (FileReferenceTD) item.getValue();
node.setAssociatedFilename(fileReference.getFileName());
node.setDataReferenceId(fileReference.getFileReference());
node.setTitle(StringUtils.format(LEAF_TEXT_FORMAT, item.getEndpointName(), fileReference.getFileName()));
}
}
private static void addDirectoryReference(EndpointHistoryDataItem item, DMBrowserNode node) {
if (item.getValue() instanceof DirectoryReferenceTD) {
DirectoryReferenceTD directoryReference = (DirectoryReferenceTD) item.getValue();
node.setAssociatedFilename(directoryReference.getDirectoryName());
node.setDataReferenceId(directoryReference.getDirectoryReference());
node.setTitle(StringUtils.format(LEAF_TEXT_FORMAT, item.getEndpointName(), directoryReference.getDirectoryName()));
node.setDirectoryReferenceTD((DirectoryReferenceTD) item.getValue());
}
}
private static String getAbbreviatedContent(TypedDatum datum) {
DataType dataType = datum.getDataType();
switch (dataType) {
case SmallTable:
SmallTableTD table = (SmallTableTD) datum;
return table.toLengthLimitedString(MAX_LABEL_LENGTH);
case Vector:
VectorTD vector = (VectorTD) datum;
return vector.toLengthLimitedString(MAX_LABEL_LENGTH);
case Matrix:
MatrixTD matrix = (MatrixTD) datum;
return matrix.toLengthLimitedString(MAX_LABEL_LENGTH);
default:
return datum.toString();
}
}
private static String handleBooleanDigitShortTextLabel(EndpointHistoryDataItem item, String endpointName, DMBrowserNode node) {
String fullContent = item.getValue().toString();
return handleLabel(fullContent, org.apache.commons.lang3.StringUtils.abbreviate(fullContent, MAX_LABEL_LENGTH), endpointName, node);
}
private static void handleNotAValueLabel(EndpointHistoryDataItem item, String endpointName, DMBrowserNode node) {
NotAValueTD notAValue = (NotAValueTD) item.getValue();
String labelText = notAValue.toString();
if (notAValue.getCause().equals(NotAValueTD.Cause.Failure)) {
labelText += " [cause: some component failed]";
} else {
labelText += " [cause: explicitly sent by some component]";
}
node.setTitle(StringUtils.format(LEAF_TEXT_FORMAT, endpointName, labelText));
}
private static String handleSmallTableLabel(EndpointHistoryDataItem item, String endpointName, DMBrowserNode node) {
SmallTableTD table = (SmallTableTD) item.getValue();
String abbreviatedContent = table.toLengthLimitedString(MAX_LABEL_LENGTH);
if (table.getColumnCount() * table.getRowCount() > MAX_NON_PERSISTENT_ENTRIES) {
node.setSmallTableTDAndFileName(table, endpointName);
return handleLabel(abbreviatedContent, endpointName, node);
} else {
return handleLabel(table.toString(), abbreviatedContent, endpointName, node);
}
}
private static String handleMatrixLabel(EndpointHistoryDataItem item, String endpointName, DMBrowserNode node) {
MatrixTD matrix = (MatrixTD) item.getValue();
String abbreviatedContent = matrix.toLengthLimitedString(MAX_LABEL_LENGTH);
if (matrix.getRowDimension() * matrix.getColumnDimension() > MAX_NON_PERSISTENT_ENTRIES) {
node.setMatrixTDAndFileName(matrix, endpointName);
return handleLabel(abbreviatedContent, endpointName, node);
} else {
return handleLabel(matrix.toString(), abbreviatedContent, endpointName, node);
}
}
private static String handleVectorLabel(EndpointHistoryDataItem item, String endpointName, DMBrowserNode node) {
VectorTD vector = (VectorTD) item.getValue();
String abbreviatedContent = vector.toLengthLimitedString(MAX_LABEL_LENGTH);
if (vector.getRowDimension() > MAX_NON_PERSISTENT_ENTRIES) {
node.setVectorTDAndFileName(vector, endpointName);
return handleLabel(abbreviatedContent, endpointName, node);
} else {
return handleLabel(vector.toString(), abbreviatedContent, endpointName, node);
}
}
private static String handleLabel(String abbreviatedContent, String endpointName, DMBrowserNode node) {
String formattedLabel = StringUtils.format(LEAF_TEXT_FORMAT, endpointName, abbreviatedContent);
node.setTitle(formattedLabel);
return formattedLabel;
}
private static String handleLabel(String fullContent, String abbreviatedContent, String endpointName, DMBrowserNode node) {
node.setFileContentAndName(fullContent, endpointName);
return handleLabel(abbreviatedContent, endpointName, node);
}
private static void handleDataItem(EndpointHistoryDataItem item, String name, DMBrowserNode parent,
String historyItemDataReferenceId, EndpointType endpointType, Map<String, String> endpointMetaData) {
boolean hasMetaData = endpointMetaData != null && !endpointMetaData.isEmpty();
//Handle older DB entries that did not store the endpoint data type, but only the typed datum data type (<RCE 8.0)
DataType currentDataType = item.getValue().getDataType();
if (hasMetaData) {
if (endpointMetaData.containsKey(MetaDataKeys.DATA_TYPE) && currentDataType != DataType.NotAValue) {
//Replace currentDataType if the data type of the endoint is stored in the db (>RCE 8.0)
currentDataType = DataType.byShortName(endpointMetaData.get(MetaDataKeys.DATA_TYPE));
if (!item.getValue().getDataType().equals(currentDataType)) {
try {
// create a dummy instance; this is required by the OSGi ServiceRegistryAccess to determine the caller's bundle -
// flink
CommonHistoryDataItemSubtreeBuilderUtils dummyInstance = new CommonHistoryDataItemSubtreeBuilderUtils();
ServiceRegistryAccess registryAccess = ServiceRegistry.createAccessFor(dummyInstance);
TypedDatumConverter converter = registryAccess.getService(TypedDatumService.class).getConverter();
endpointMetaData.put(MetaDataKeys.DATA_TYPE_CONVERSION,
StringUtils.format(
STRING_CONVERSION_INFORMATION,
item.getValue().getDataType().getDisplayName(), getAbbreviatedContent(item.getValue())));
item = new EndpointHistoryDataItem(item.getTimestamp(), item.getEndpointName(),
converter.castOrConvert(item.getValue(), currentDataType));
} catch (DataTypeException e) {
throw new RuntimeException(
StringUtils.format(NOT_CONVERTIBLE_MESSAGE, item.getValue().getDataType(), currentDataType, name));
}
}
}
}
DMBrowserNodeType type = getDMBrowserNodeTypeByDataType(currentDataType);
DMBrowserNode node = null;
if (currentDataType != DataType.DirectoryReference && (!hasMetaDataToDisplay(endpointMetaData))) {
node = DMBrowserNode.addNewLeafNode(name, type, parent);
} else if (hasMetaDataToDisplay(endpointMetaData)) {
DMBrowserNode outputNameNode = DMBrowserNode.addNewChildNode(name, type, parent);
node = DMBrowserNode.addNewLeafNode(name, type, outputNameNode);
} else {
node = DMBrowserNode.addNewChildNode(name, type, parent);
}
switch (currentDataType) {
case SmallTable:
handleSmallTableLabel(item, name, node);
break;
case Vector:
handleVectorLabel(item, name, node);
break;
case ShortText:
case Boolean:
case Integer:
case Float:
handleBooleanDigitShortTextLabel(item, name, node);
break;
case NotAValue:
handleNotAValueLabel(item, name, node);
break;
case Matrix:
handleMatrixLabel(item, name, node);
break;
case FileReference:
addFileReference(item, node);
break;
case DirectoryReference:
addDirectoryReference(item, node);
break;
default:
node.setTitle(StringUtils.format(LEAF_TEXT_FORMAT, name, item.getValue()));
}
if (hasMetaDataToDisplay(endpointMetaData)) {
for (Entry<String, String> property : endpointMetaData.entrySet()) {
if (!property.getKey().equals(MetaDataKeys.DATA_TYPE)) {
try {
JsonNode tree = mapper.readTree(property.getValue());
DMBrowserNode.addNewLeafNode(StringUtils.format("%s: %s", tree.get("guiName").asText(), tree.get("value").asText()),
DMBrowserNodeType.InformationText, node.getParent());
} catch (IOException e) {
logger.error("Could not parse endpoint properties from json string " + property.getValue());
}
}
}
}
}
private static boolean hasMetaDataToDisplay(Map<String, String> endpointMetaData) {
if (endpointMetaData != null && !endpointMetaData.isEmpty()) {
for (String key : endpointMetaData.keySet()) {
if (!META_DATA_KEYS_TO_HIDE.contains(key)) {
return true;
}
}
}
return false;
}
/**
* Builds directory subtree for endpoint.
*
* @param directoryReference {@link DirectoryReferenceTD} pointing to the directory in the data management
* @param node {@link DMBrowserNode} of the endpoint
* @param parent parent {@link DMBrowserNode} of the endpoint {@link DMBrowserNode}
*/
public static void buildSubtreeForDirectoryItem(DirectoryReferenceTD directoryReference, DMBrowserNode node, DMBrowserNode parent) {
File dir = new File(Activator.getInstance().getBundleSpecificTempDir(), directoryReference.getDirectoryReference());
if (!dir.mkdir() && (!dir.exists() || dir.isFile())) {
logger.error("Temp directory could not be created or did already exist as file: " + dir);
return;
}
// Data reference ids are assumed to be unique. The folder name equals the data reference id of the directory. So, it is assumed
// that if the folder is not empty its content was already created in previous workflow data browsing and is not fetched anew -
// seid_do
if (dir.list().length == 0) {
// create a dummy instance; this is required by the OSGi ServiceRegistryAccess to determine the caller's bundle - misc_ro
CommonHistoryDataItemSubtreeBuilderUtils dummyInstance = new CommonHistoryDataItemSubtreeBuilderUtils();
ServiceRegistryAccess serviceRegistryAccess = ServiceRegistry.createAccessFor(dummyInstance);
ComponentDataManagementService componentService = serviceRegistryAccess.getService(ComponentDataManagementService.class);
try {
componentService.copyDirectoryReferenceTDToLocalDirectory(directoryReference, dir,
parent.getNodeWithTypeWorkflow().getNodeIdentifier());
} catch (IOException e) {
logger.error("Copying directory from data management to the file system failed", e);
}
}
if (dir != null) {
File f = new File(dir, directoryReference.getDirectoryName());
if (f.listFiles() != null && f.listFiles().length > 0) {
recursiveBrowseDirectory(new File(dir, directoryReference.getDirectoryName()), node);
} else {
node.markAsLeaf();
}
}
}
private static void recursiveBrowseDirectory(File parentFile, DMBrowserNode parentNode) {
for (File file : parentFile.listFiles()) {
// if file is directory and contains something, add child node and go on
if (file.isDirectory()) {
if (file.listFiles().length > 0) {
DMBrowserNode node = DMBrowserNode.addNewChildNode(file.getName(), DMBrowserNodeType.DMDirectoryReference, parentNode);
recursiveBrowseDirectory(file, node);
} else {
DMBrowserNode.addNewLeafNode(file.getName(), DMBrowserNodeType.DMDirectoryReference, parentNode);
}
} else {
// else add leaf node for each item and finish
DMBrowserNode node = DMBrowserNode.addNewLeafNode(file.getName(), DMBrowserNodeType.DMFileResource, parentNode);
node.setAssociatedFilename(file.getName());
node.setFileReferencePath(file.getAbsolutePath());
}
}
}
}