/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.component.workflow.model.api; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.communication.api.NodeIdentifierService; import de.rcenvironment.core.communication.common.LogicalNodeId; import de.rcenvironment.core.communication.common.NodeIdentifierContextHolder; import de.rcenvironment.core.component.model.api.ComponentDescription; import de.rcenvironment.core.component.model.endpoint.api.EndpointDescription; import de.rcenvironment.core.component.model.spi.PropertiesChangeSupport; import de.rcenvironment.core.component.workflow.api.WorkflowConstants; import de.rcenvironment.core.component.workflow.execution.api.EndpointChangeListener; import de.rcenvironment.core.utils.common.StringUtils; /** * Describes a {@link WorkflowController} in a way that can be used by a {@link WorkflowRegistry} to create that {@link WorkflowController}. * * @author Roland Gude * @author Heinrich Wendel * @author Doreen Seider * @author Sascha Zur */ public class WorkflowDescription extends PropertiesChangeSupport implements Serializable, Cloneable { /** Property that is fired when a WorkflowNode was added. */ public static final String PROPERTY_NODES_OR_CONNECTIONS = "de.rcenvironment.wf.n_cn"; /** Property that is fired when a WorkflowNode was added. */ public static final String PROPERTY_NODES = "e.rcenvironment.wf.n"; /** Property that is fired when a WorkflowNode was removed. */ public static final String PROPERTY_CONNECTIONS = "e.rcenvironment.wf.cn"; /** Property that is fired when a WorkflowLabel was removed. */ public static final String PROPERTY_LABEL = "e.rcenvironment.wf.l"; protected static final int MINUS_ONE = -1; private static final long serialVersionUID = 339866937554580256L; private final String identifier; private int workflowVersionNumber = WorkflowConstants.INITIAL_WORKFLOW_VERSION_NUMBER; private String name; private String fileName; private String additionalInformation; private LogicalNodeId controllerNode; private boolean isControllerNodeIdTransient = false; private final List<WorkflowNode> nodes = new ArrayList<WorkflowNode>(); private final List<Connection> connections = new ArrayList<Connection>(); // removed temporarily the final declaration to initialize the labels later on due to backwards compatibility: <=6.1. -seid_do, April // 2014 private List<WorkflowLabel> labels = new ArrayList<WorkflowLabel>(); /** * @param identifier The identifier of the {@link WorkflowDescription}. */ public WorkflowDescription(String identifier) { this.identifier = identifier; } public String getIdentifier() { return identifier; } public int getWorkflowVersion() { return workflowVersionNumber; } public void setWorkflowVersion(Integer workflowVersion) { workflowVersionNumber = workflowVersion; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public String getAdditionalInformation() { return additionalInformation; } public void setAdditionalInformation(final String additionalInformation) { this.additionalInformation = additionalInformation; } public LogicalNodeId getControllerNode() { return controllerNode; } public void setControllerNode(LogicalNodeId logicalNodeId) { this.controllerNode = logicalNodeId; } public boolean getIsControllerNodeIdTransient() { return isControllerNodeIdTransient; } public void setIsControllerNodeIdTransient(boolean isControllerNodeIdTransient) { this.isControllerNodeIdTransient = isControllerNodeIdTransient; } /** * @return {@link List} of {@link WorkflowNode}s; modification to the {@link List} don't affect the {@link WorkflowNode}s of this * {@link WorkflowDescription} */ public List<WorkflowNode> getWorkflowNodes() { return new ArrayList<>(nodes); } /** * Sorts the list of {@link WorkflowNode}s regarding the z-index. */ public void sortWorkflowNodesByZIndex() { Collections.sort(nodes, new Comparator<WorkflowNode>() { @Override public int compare(WorkflowNode arg0, WorkflowNode arg1) { // this automatically puts new nodes to the foreground if (arg0.getZIndex() == MINUS_ONE) { return 1; } else if (arg0.getZIndex() < arg1.getZIndex()) { return MINUS_ONE; } else if (arg0.getZIndex() > arg1.getZIndex()) { return 1; } else { return 0; } } }); // Normalize layers lowest: z = 0 for (int i = 0; i < nodes.size(); i++) { nodes.get(i).setZIndex(i); } } /** * @return {@link List} of {@link WorkflowLabel}s; modification to the {@link List} don't affect the {@link WorkflowLabel}s of this * {@link WorkflowDescription} */ public List<WorkflowLabel> getWorkflowLabels() { return new ArrayList<>(labels); } /** * Sorts the list of {@link WorkflowLabel}s regarding the z-index. */ public void sortWorkflowLabelsByZIndex() { Collections.sort(labels, new Comparator<WorkflowLabel>() { @Override public int compare(WorkflowLabel arg0, WorkflowLabel arg1) { // this automatically puts new labels to the foreground if (arg0.getZIndex() == MINUS_ONE) { return 1; } else if (arg0.getZIndex() < arg1.getZIndex()) { return MINUS_ONE; } else if (arg0.getZIndex() > arg1.getZIndex()) { return 1; } else { return 0; } } }); // Normalize layers lowest: z = 0 for (int i = 0; i < labels.size(); i++) { labels.get(i).setZIndex(i); } } /** * Adds a new label to the list of @link {@link WorkflowLabel}s and fires a property change event. * * @param label the new label */ public void addWorkflowLabel(WorkflowLabel label) { labels.add(label); firePropertyChange(PROPERTY_LABEL); } /** * Adds a labels to the list of @link {@link WorkflowLabel}s and fires a property change event. * * @param labelsToAdd the new labels */ public void addWorkflowLabels(List<WorkflowLabel> labelsToAdd) { for (WorkflowLabel label : labelsToAdd) { labels.add(label); } firePropertyChange(PROPERTY_LABEL); } /** * Set new labels. It replaces the current list of @link {@link WorkflowLabel}s and fires a property change event. * * @param labelsToSet the new labels to set */ public void setWorkflowLabels(List<WorkflowLabel> labelsToSet) { labels = labelsToSet; firePropertyChange(PROPERTY_LABEL); } /** * Returns the {@link WorkflowNode} with the given identifier. * * @param nodeId the identifier of the desired {@link WorkflowNode} * @return the {@link WorkflowNode} with the given identifier * @throws IllegalArgumentException if no {@link WorkflowNode} with the given identifier exists */ public WorkflowNode getWorkflowNode(final String nodeId) throws IllegalArgumentException { for (WorkflowNode node : nodes) { if (node.getIdentifier().equals(nodeId)) { return node; } } throw new IllegalArgumentException(StringUtils.format("No node with identifier %s found", nodeId)); } private void addWorkflowNodeWithoutNotify(WorkflowNode node) { nodes.add(node); EndpointChangeListener l = new EndpointChangeListener(this); node.getInputDescriptionsManager().addPropertyChangeListener(l); node.getOutputDescriptionsManager().addPropertyChangeListener(l); } /** * Adds a new {@link WorkflowNode}. * * @param node The new Workflow node. */ public void addWorkflowNode(WorkflowNode node) { addWorkflowNodeWithoutNotify(node); firePropertyChange(PROPERTY_NODES_OR_CONNECTIONS); firePropertyChange(PROPERTY_NODES); } /** * Adds a list of new {@link WorkflowNode}s. * * @param nodesToAdd The list of new Workflow nodes. */ public void addWorkflowNodes(List<WorkflowNode> nodesToAdd) { for (WorkflowNode node : nodesToAdd) { nodes.add(node); EndpointChangeListener l = new EndpointChangeListener(this); node.getInputDescriptionsManager().addPropertyChangeListener(l); node.getOutputDescriptionsManager().addPropertyChangeListener(l); } firePropertyChange(PROPERTY_NODES_OR_CONNECTIONS); firePropertyChange(PROPERTY_NODES); } /** * Adds a new list of {@link Connection}s at once. * * @param nodeToAdd {@link WorkflowNode} to add * @param connectionsToAdd The list of {@link Connection}s to add. */ public void addWorkflowNodeAndConnections(WorkflowNode nodeToAdd, List<Connection> connectionsToAdd) { addWorkflowNodeWithoutNotify(nodeToAdd); addConnectionsWithoutNotify(connectionsToAdd); firePropertyChange(PROPERTY_NODES_OR_CONNECTIONS); firePropertyChange(PROPERTY_CONNECTIONS); firePropertyChange(PROPERTY_NODES); } /** * Adds a new list of {@link Connection}s at once. * * @param nodesToAdd {@link WorkflowNode}s to add * @param connectionsToAdd The list of {@link Connection}s to add. */ public void addWorkflowNodesAndConnections(List<WorkflowNode> nodesToAdd, List<Connection> connectionsToAdd) { for (WorkflowNode nodeToAdd : nodesToAdd) { addWorkflowNodeWithoutNotify(nodeToAdd); } addConnectionsWithoutNotify(connectionsToAdd); firePropertyChange(PROPERTY_NODES_OR_CONNECTIONS); firePropertyChange(PROPERTY_CONNECTIONS); firePropertyChange(PROPERTY_NODES); } /** * Removes a {@link WorkflowLabel}. * * @param label The {@link WorkflowLabel} to remove. */ public void removeWorkflowLabel(WorkflowLabel label) { labels.remove(label); firePropertyChange(PROPERTY_LABEL); } /** * Removes a {@link WorkflowLabel}. * * @param labelsToRemove The {@link WorkflowLabel}s to remove. */ public void removeWorkflowLabels(List<WorkflowLabel> labelsToRemove) { for (WorkflowLabel label : labelsToRemove) { labels.remove(label); } firePropertyChange(PROPERTY_LABEL); } /** * Removes a {@link WorkflowNode}. * * @param node The {@link WorkflowNode} to remove. */ public void removeWorkflowNode(WorkflowNode node) { nodes.remove(node); firePropertyChange(PROPERTY_NODES_OR_CONNECTIONS); firePropertyChange(PROPERTY_NODES); } /** * Removes a {@link WorkflowNode} and its {@link Connection}s. * * @param node {@link WorkflowNode} to delete */ private List<Connection> removeWorkflowNodeAndRelatedConnectionsWithoutNotify(WorkflowNode node) { List<Connection> cnsToDelete = new ArrayList<>(); for (Connection cn : getConnections()) { if (cn.getTargetNode().equals(node) || cn.getSourceNode().equals(node)) { cnsToDelete.add(cn); } } removeConnectionsWithoutNotify(cnsToDelete); nodes.remove(node); return cnsToDelete; } /** * Removes a {@link WorkflowNode} and its {@link Connection}s. * * @param node {@link WorkflowNode} to delete * @return List of {@link Connection}s deleted */ public List<Connection> removeWorkflowNodeAndRelatedConnections(WorkflowNode node) { List<Connection> cnsDeleted = removeWorkflowNodeAndRelatedConnectionsWithoutNotify(node); if (!cnsDeleted.isEmpty()) { firePropertyChange(PROPERTY_CONNECTIONS); } firePropertyChange(PROPERTY_NODES_OR_CONNECTIONS); firePropertyChange(PROPERTY_NODES); return cnsDeleted; } /** * Removes a list of {@link WorkflowNode}s and their {@link Connection}s. * * @param nodesToRemove The list of {@link WorkflowNode}s to remove. * @return List of {@link Connection}s deleted */ public List<Connection> removeWorkflowNodesAndRelatedConnections(List<WorkflowNode> nodesToRemove) { List<Connection> cnsDeleted = new ArrayList<>(); for (WorkflowNode node : nodesToRemove) { cnsDeleted.addAll(removeWorkflowNodeAndRelatedConnectionsWithoutNotify(node)); } if (!cnsDeleted.isEmpty()) { firePropertyChange(PROPERTY_CONNECTIONS); } firePropertyChange(PROPERTY_NODES_OR_CONNECTIONS); firePropertyChange(PROPERTY_NODES); return cnsDeleted; } /** * Removes a list of {@link WorkflowNode}s and their {@link Connection}s. * * @param nodesToRemove The list of {@link WorkflowNode}s to remove. * @return List of {@link Connection}s deleted */ public List<Connection> removeWorkflowNodesAndRelatedConnectionsWithoutNotify(List<WorkflowNode> nodesToRemove) { List<Connection> cnsDeleted = new ArrayList<>(); for (WorkflowNode node : nodesToRemove) { cnsDeleted.addAll(removeWorkflowNodeAndRelatedConnectionsWithoutNotify(node)); } return cnsDeleted; } /** * Removes a list of {@link WorkflowNode}s. * * @param nodesToRemove The list of {@link WorkflowNode}s to remove. */ public void removeWorkflowNodes(List<WorkflowNode> nodesToRemove) { for (WorkflowNode node : nodesToRemove) { nodes.remove(node); } firePropertyChange(PROPERTY_NODES_OR_CONNECTIONS); firePropertyChange(PROPERTY_NODES); } /** * Removes a all {@link WorkflowNode}. */ public void removeAllWorkflowNodes() { nodes.clear(); firePropertyChange(PROPERTY_NODES_OR_CONNECTIONS); firePropertyChange(PROPERTY_NODES); } /** * Returns all {@link Connection}s. * * @return all {@link Connection}s. */ public List<Connection> getConnections() { return new ArrayList<Connection>(connections); } /** * Adds a new {@link Connection}. * * @param connection The {@link Connection} to add. */ public void addConnection(Connection connection) { addConnectionWithoutNotify(connection); firePropertyChange(PROPERTY_NODES_OR_CONNECTIONS); firePropertyChange(PROPERTY_CONNECTIONS); } /** * Adds a new list of {@link Connection}s at once. * * @param connectionsToAdd The list of {@link Connection}s to add. */ public void addConnections(List<Connection> connectionsToAdd) { addConnectionsWithoutNotify(connectionsToAdd); firePropertyChange(PROPERTY_NODES_OR_CONNECTIONS); firePropertyChange(PROPERTY_CONNECTIONS); } private void addConnectionsWithoutNotify(List<Connection> connectionsToAdd) { for (Connection connection : connectionsToAdd) { addConnectionWithoutNotify(connection); } } private void addConnectionWithoutNotify(Connection connection) { connections.add(connection); EndpointDescription output = connection.getOutput(); EndpointDescription input = connection.getInput(); getWorkflowNode(connection.getSourceNode().getIdentifier()).getOutputDescriptionsManager() .addConnectedDataType(output.getName(), input.getDataType()); getWorkflowNode(connection.getTargetNode().getIdentifier()).getInputDescriptionsManager() .addConnectedDataType(input.getName(), output.getDataType()); } /** * Removes a {@link Connection}. * * @param connection The {@link Connection} to remove. */ public void removeConnection(Connection connection) { if (removeConnectionWithoutNotify(connection)) { firePropertyChange(PROPERTY_NODES_OR_CONNECTIONS); firePropertyChange(PROPERTY_CONNECTIONS); } } /** * Removes a list of {@link Connection}s at once. * * @param connectionsToRemove The list of {@link Connection}s to remove. */ public void removeConnections(List<Connection> connectionsToRemove) { boolean removed = false; for (Connection connection : connectionsToRemove) { if (removeConnectionWithoutNotify(connection)) { removed = true; } } if (removed) { firePropertyChange(PROPERTY_NODES_OR_CONNECTIONS); firePropertyChange(PROPERTY_CONNECTIONS); } } private boolean removeConnectionWithoutNotify(Connection connection) { EndpointDescription output = connection.getOutput(); EndpointDescription input = connection.getInput(); connection.getSourceNode().setValid(false); connection.getTargetNode().setValid(false); getWorkflowNode(connection.getSourceNode().getIdentifier()).getOutputDescriptionsManager() .removeConnectedDataType(output.getName(), input.getDataType()); getWorkflowNode(connection.getTargetNode().getIdentifier()).getInputDescriptionsManager() .removeConnectedDataType(input.getName(), output.getDataType()); return connections.remove(connection); } private boolean removeConnectionsWithoutNotify(List<Connection> connectionsToRemove) { boolean removed = false; for (Connection connection : connectionsToRemove) { if (removeConnectionWithoutNotify(connection)) { removed = true; } } return removed; } /** * Removes a {@link Connection}. * * @param connectionsToAdd The list of {@link Connection}s to add. */ public void replaceConnections(List<Connection> connectionsToAdd) { removeConnectionsWithoutNotify(getConnections()); addConnections(connectionsToAdd); } /** * Copies the execution information of the given workflow description. It includes name, controller's node, component's node and * "additional information". * * @param wd workflow description with execution information to copy */ public void copyExecutionInformationFromWorkflowDescription(WorkflowDescription wd) { setName(wd.getName()); setControllerNode(wd.getControllerNode()); setAdditionalInformation(wd.getAdditionalInformation()); for (WorkflowNode node : getWorkflowNodes()) { if (wd.getWorkflowNode(node.getIdentifier()) != null) { node.getComponentDescription().setComponentInstallation( wd.getWorkflowNode(node.getIdentifier()).getComponentDescription().getComponentInstallation()); } } } /** * Clones a given {@link WorkflowDescription}. Note that this requires a {@link NodeIdentifierService} instance to be avilable to the * current {@link Thread}; see {@link NodeIdentifierContextHolder} for how to set it. * * @return the cloned {@link WorkflowDescription}. */ @Override public WorkflowDescription clone() { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); oos.flush(); ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bin); WorkflowDescription wd = (WorkflowDescription) ois.readObject(); ois.close(); bin.close(); oos.close(); bos.close(); return wd; } catch (IOException e) { LogFactory.getLog(ComponentDescription.class).error("Failed to clone workflow description", e); throw new RuntimeException(e); } catch (ClassNotFoundException e) { LogFactory.getLog(ComponentDescription.class).error("Failed to clone workflow description", e); throw new RuntimeException(e); } } }