/**
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.airavata.workflow.model.graph.impl;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.apache.airavata.common.utils.XMLUtil;
import org.apache.airavata.workflow.model.exceptions.WorkflowRuntimeException;
import org.apache.airavata.workflow.model.graph.ControlPort;
import org.apache.airavata.workflow.model.graph.DataEdge;
import org.apache.airavata.workflow.model.graph.DataPort;
import org.apache.airavata.workflow.model.graph.Edge;
import org.apache.airavata.workflow.model.graph.Graph;
import org.apache.airavata.workflow.model.graph.GraphException;
import org.apache.airavata.workflow.model.graph.GraphFactory;
import org.apache.airavata.workflow.model.graph.GraphSchema;
import org.apache.airavata.workflow.model.graph.Node;
import org.apache.airavata.workflow.model.graph.Port;
import org.apache.airavata.workflow.model.graph.Port.Kind;
import org.apache.airavata.workflow.model.graph.system.InputNode;
import org.apache.airavata.workflow.model.graph.system.OutputNode;
import org.apache.airavata.workflow.model.graph.system.StreamSourceNode;
import org.apache.airavata.workflow.model.graph.system.SystemDataPort;
import org.apache.airavata.workflow.model.graph.util.GraphUtil;
import org.apache.airavata.workflow.model.graph.ws.WSPort;
import org.apache.airavata.workflow.model.utils.ApplicationVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmlpull.infoset.XmlElement;
public abstract class GraphImpl implements Graph {
private static final Logger logger = LoggerFactory.getLogger(GraphImpl.class);
/**
* Unique ID of this workflow
*/
private String id;
/**
* Name of the workflow. It has to have one.
*/
private String name = "Workflow";
/**
* Default to empty string to avoid null check.
*/
private String description = "";
private List<NodeImpl> nodes = new LinkedList<NodeImpl>();
private List<PortImpl> ports = new LinkedList<PortImpl>();
private List<EdgeImpl> edges = new LinkedList<EdgeImpl>();
private GraphFactory factory;
/**
* @param factory
*/
public GraphImpl(GraphFactory factory) {
this.factory = factory;
}
/**
* @see org.apache.airavata.workflow.model.graph.Graph#getID()
*/
public String getID() {
if (this.id == null) {
this.id = this.name;
// If its still null
if (null == this.id) {
throw new WorkflowRuntimeException("The workflow ID is null");
}
}
return this.id;
}
/**
* This will only be done for the ODE
*
* @param id
*/
public void setID(String id) {
this.id = id;
}
/**
* @see org.apache.airavata.workflow.model.graph.Graph#setName(java.lang.String)
*/
public void setName(String name) {
this.name = name;
}
/**
* @see org.apache.airavata.workflow.model.graph.Graph#getName()
*/
public String getName() {
return this.name;
}
/**
* @see org.apache.airavata.workflow.model.graph.Graph#getDescription()
*/
public String getDescription() {
return this.description;
}
/**
* @see org.apache.airavata.workflow.model.graph.Graph#setDescription(java.lang.String)
*/
public void setDescription(String description) {
this.description = description;
}
/**
* @see org.apache.airavata.workflow.model.graph.Graph#getNodes()
*/
public List<NodeImpl> getNodes() {
return this.nodes;
}
/**
* @see org.apache.airavata.workflow.model.graph.Graph#getPorts()
*/
public List<PortImpl> getPorts() {
return this.ports;
}
/**
* @see org.apache.airavata.workflow.model.graph.Graph#getEdges()
*/
public List<EdgeImpl> getEdges() {
return this.edges;
}
/**
* @see org.apache.airavata.workflow.model.graph.Graph#removeNode(org.apache.airavata.workflow.model.graph.Node)
*/
public void removeNode(Node node) throws GraphException {
if (node == null) {
throw new IllegalArgumentException("null");
}
if (!this.nodes.contains(node)) {
throw new GraphException("The graph doesn't contain the node that is being removed.");
}
NodeImpl nodeImpl = (NodeImpl) node;
// Have to be very careful to remove the node.
// The extended for loop cannot be used to remove the elements.
// Remove edges connected to input ports.
for (Iterator<DataPort> portItr = nodeImpl.getInputPorts().iterator(); portItr.hasNext();) {
DataPort port = portItr.next();
for (Iterator<DataEdge> edgeItr = port.getEdges().iterator(); edgeItr.hasNext();) {
DataEdge edge = edgeItr.next();
// Remove the edge from from-port.
DataPort fromPort = edge.getFromPort();
fromPort.removeEdge(edge);
// remove the edge from this port. This is necessary so that
// type update works properly.
edgeItr.remove();
// Remove the edge from the graph.
this.edges.remove(edge);
fromPort.getNode().edgeWasRemoved(edge);
}
// Remove the port from the node.
portItr.remove();
// Remove the port from the graph.
this.ports.remove(port);
}
// Remove edges connected to output ports.
for (Iterator<DataPort> portItr = nodeImpl.getOutputPorts().iterator(); portItr.hasNext();) {
DataPort port = portItr.next();
for (Iterator<DataEdge> edgeItr = port.getEdges().iterator(); edgeItr.hasNext();) {
DataEdge edge = edgeItr.next();
DataPort toPort = edge.getToPort();
toPort.removeEdge(edge);
edgeItr.remove();
this.edges.remove(edge);
toPort.getNode().edgeWasRemoved(edge);
}
portItr.remove();
this.ports.remove(port);
}
for (Iterator<ControlPort> portItr = nodeImpl.getControlOutPorts().iterator(); portItr.hasNext();) {
PortImpl port = portItr.next();
for (Iterator<? extends EdgeImpl> edgeItr = port.getEdges().iterator(); edgeItr.hasNext();) {
EdgeImpl edge = edgeItr.next();
PortImpl toPort = edge.getToPort();
toPort.removeEdge(edge);
edgeItr.remove();
this.edges.remove(edge);
toPort.getNode().edgeWasRemoved(edge);
}
portItr.remove();
this.ports.remove(port);
}
PortImpl controlInPort = nodeImpl.getControlInPort();
if (controlInPort != null) {
for (Iterator<? extends EdgeImpl> edgeItr = controlInPort.getEdges().iterator(); edgeItr.hasNext();) {
EdgeImpl edge = edgeItr.next();
PortImpl fromPort = edge.getFromPort();
fromPort.removeEdge(edge);
edgeItr.remove();
this.edges.remove(edge);
fromPort.getNode().edgeWasRemoved(edge);
}
this.ports.remove(controlInPort);
}
PortImpl eprPort = nodeImpl.getEPRPort();
if (eprPort != null) {
for (Iterator<? extends EdgeImpl> edgeItr = eprPort.getEdges().iterator(); edgeItr.hasNext();) {
EdgeImpl edge = edgeItr.next();
PortImpl toPort = edge.getToPort();
toPort.removeEdge(edge);
edgeItr.remove();
this.edges.remove(edge);
toPort.getNode().edgeWasRemoved(edge);
}
this.ports.remove(eprPort);
}
this.nodes.remove(node);
}
/**
* @see org.apache.airavata.workflow.model.graph.Graph#getNode(java.lang.String)
*/
public NodeImpl getNode(String nodeID) {
for (NodeImpl node : this.nodes) {
if (nodeID.equals(node.getID())) {
return node;
}
}
return null;
}
/**
* @param port
* @throws GraphException
*/
public void removePort(Port port) throws GraphException {
if (port == null) {
throw new IllegalArgumentException("null");
}
if (!this.ports.contains(port)) {
throw new GraphException("The graph doesn't contain the port that is being removed.");
}
// copy it so that we can remove edge without worrying about the
// iteration issue.
ArrayList<Edge> edgesToBeRemoved = new ArrayList<Edge>(port.getEdges());
for (Edge edge : edgesToBeRemoved) {
removeEdge(edge);
}
this.ports.remove(port);
}
/**
* @see org.apache.airavata.workflow.model.graph.Graph#getPort(java.lang.String)
*/
public PortImpl getPort(String portID) {
for (PortImpl port : this.ports) {
if (portID.equals(port.getID())) {
return port;
}
}
return null;
}
/**
* @see org.apache.airavata.workflow.model.graph.Graph#addEdge(org.apache.airavata.workflow.model.graph.Port,
* org.apache.airavata.workflow.model.graph.Port)
*/
public Edge addEdge(Port fromPort, Port toPort) throws GraphException {
if (containsEdge(fromPort, toPort)) {
// The edge already exists. Doesn't create a new one.
return null;
}
if (!this.ports.contains(fromPort) || !this.ports.contains(toPort)) {
// The specified port doesn't belong to the graph.
throw new GraphException("The graph doesn't contain the specified port.");
}
PortImpl fromPortImpl = (PortImpl) fromPort;
PortImpl toPortImpl = (PortImpl) toPort;
NodeImpl fromNode = fromPortImpl.getNode();
NodeImpl toNode = toPortImpl.getNode();
EdgeImpl edge = this.factory.createEdge(fromPort, toPort);
edge.setFromPort(fromPortImpl);
edge.setToPort(toPortImpl);
fromPortImpl.addEdge(edge);
toPortImpl.addEdge(edge);
addEdge(edge);
try {
fromNode.edgeWasAdded(edge);
toNode.edgeWasAdded(edge);
return edge;
} catch (GraphException e) {
removeEdge(edge);
throw e;
}
}
/**
* @throws GraphException
* @see org.apache.airavata.workflow.model.graph.Graph#removeEdge(org.apache.airavata.workflow.model.graph.Edge)
*/
public void removeEdge(Edge edge) throws GraphException {
if (!this.edges.contains(edge)) {
throw new GraphException("The graph doesn't contain the specified edge.");
}
EdgeImpl edgeImpl = (EdgeImpl) edge;
PortImpl fromPort = edgeImpl.getFromPort();
PortImpl toPort = edgeImpl.getToPort();
NodeImpl fromNode = fromPort.getNode();
NodeImpl toNode = toPort.getNode();
fromPort.removeEdge(edgeImpl);
toPort.removeEdge(edgeImpl);
this.edges.remove(edgeImpl);
// This has to be after removing edges.
fromNode.edgeWasRemoved(edge);
toNode.edgeWasRemoved(edge);
}
/**
* @throws GraphException
* @see org.apache.airavata.workflow.model.graph.Graph#removeEdge(org.apache.airavata.workflow.model.graph.Port,
* org.apache.airavata.workflow.model.graph.Port)
*/
public void removeEdge(Port fromPort, Port toPort) throws GraphException {
Collection<? extends Edge> fromEdges = fromPort.getEdges();
for (Edge fromEdge : fromEdges) {
if (fromEdge.getToPort() == toPort) {
// It's OK to remove this way because it will exit the loop
// right away.
removeEdge(fromEdge);
return;
}
}
throw new WorkflowRuntimeException("No edge exist between two ports.");
}
/**
* @see org.apache.airavata.workflow.model.graph.Graph#containsEdge(org.apache.airavata.workflow.model.graph.Port,
* org.apache.airavata.workflow.model.graph.Port)
*/
public boolean containsEdge(Port fromPort, Port toPort) {
for (Edge fromEdge : fromPort.getEdges()) {
Collection<? extends Edge> toEdges = toPort.getEdges();
if (toEdges.contains(fromEdge)) {
return true;
}
}
return false;
}
/**
* @throws GraphException
* @see org.apache.airavata.workflow.model.graph.Graph#importGraph(org.apache.airavata.workflow.model.graph.Graph)
*/
public void importGraph(Graph graph) throws GraphException {
// Does not support other implementations.
if (!(graph instanceof GraphImpl)) {
throw new GraphException("Cannot import this graph implementation");
}
GraphImpl graphImpl = (GraphImpl) graph;
for (NodeImpl node : graphImpl.getNodes()) {
addNode(node);
// Recreates the ID so that it doesn't conflict with the existing
// one.
node.createID();
// Changes the position.
Point position = node.getPosition();
node.setPosition(new Point(position.x + 5, position.y + 5));
}
for (PortImpl port : graphImpl.getPorts()) {
addPort(port);
}
for (EdgeImpl edge : graphImpl.getEdges()) {
addEdge(edge);
}
}
/**
* @see org.apache.airavata.workflow.model.graph.Graph#toXML()
*/
public XmlElement toXML() {
XmlElement graphElement = XMLUtil.BUILDER.newFragment(GraphSchema.NS, GraphSchema.GRAPH_TAG);
graphElement.setAttributeValue(GraphSchema.NS, GraphSchema.XBAYA_VERSION_ATTRIBUTE, ApplicationVersion.VERSION.getVersion());
XmlElement idElement = graphElement.addElement(GraphSchema.NS, GraphSchema.GRAPH_ID_TAG);
idElement.addChild(getID());
if (this.name != null) {
XmlElement nameElement = graphElement.addElement(GraphSchema.NS, GraphSchema.GRAPH_NAME_TAG);
nameElement.addChild(getName());
}
if (this.description != null) {
XmlElement descriptionElement = graphElement.addElement(GraphSchema.NS, GraphSchema.GRAPH_DESCRIPTION_TAG);
descriptionElement.addChild(getDescription());
}
toXML(graphElement);
for (NodeImpl node : this.nodes) {
XmlElement nodeElement = node.toXML();
graphElement.addChild(nodeElement);
}
for (PortImpl port : this.ports) {
XmlElement portElement = port.toXML();
graphElement.addChild(portElement);
}
for (EdgeImpl edge : this.edges) {
XmlElement edgeElement = edge.toXML();
graphElement.addChild(edgeElement);
}
return graphElement;
}
@Override
public JsonObject toJSON() {
JsonObject graphObject = new JsonObject();
graphObject.addProperty(GraphSchema.XBAYA_VERSION_ATTRIBUTE, ApplicationVersion.VERSION.getVersion());
graphObject.addProperty(GraphSchema.GRAPH_ID_TAG, getID());
graphObject.addProperty(GraphSchema.GRAPH_NAME_TAG, getName());
graphObject.addProperty(GraphSchema.GRAPH_DESCRIPTION_TAG, getDescription());
JsonArray nodeArray = new JsonArray();
for (NodeImpl node : this.nodes) {
nodeArray.add(node.toJSON());
}
graphObject.add(GraphSchema.NODE_TAG, nodeArray);
JsonArray portArray = new JsonArray();
for (PortImpl port : this.ports) {
portArray.add(port.toJSON());
}
graphObject.add(GraphSchema.PORT_TAG, portArray);
JsonArray edgeArray = new JsonArray();
for (EdgeImpl edge : this.edges) {
edgeArray.add(edge.toJSON());
}
graphObject.add(GraphSchema.EDGE_TAG, edgeArray);
return graphObject;
}
/**
* @param graphElement
*/
protected void toXML(@SuppressWarnings("unused") XmlElement graphElement) {
// For subclass to overwrite.
}
/**
* @param graphElement
* @throws GraphException
*/
protected void parse(XmlElement graphElement) throws GraphException {
String version = graphElement.attributeValue(GraphSchema.NS, GraphSchema.XBAYA_VERSION_ATTRIBUTE);
logger.debug("parsing a workflow created by version " + version);
XmlElement idElement = graphElement.element(GraphSchema.GRAPH_ID_TAG);
if (idElement != null) {
this.id = idElement.requiredText();
}
XmlElement nameElement = graphElement.element(GraphSchema.GRAPH_NAME_TAG);
if (nameElement != null) {
this.name = nameElement.requiredText();
}
XmlElement descriptionElement = graphElement.element(GraphSchema.GRAPH_DESCRIPTION_TAG);
if (descriptionElement != null) {
this.description = descriptionElement.requiredText();
}
for (XmlElement nodeElement : graphElement.elements(null, GraphSchema.NODE_TAG)) {
NodeImpl nodeImpl = this.factory.createNode(nodeElement);
// need to call this to set this graph to the node.
addNode(nodeImpl);
}
for (XmlElement portElement : graphElement.elements(null, GraphSchema.PORT_TAG)) {
PortImpl port = this.factory.createPort(portElement);
// need to call this to set this graph to the port.
this.addPort(port);
}
for (XmlElement edgeElement : graphElement.elements(null, GraphSchema.EDGE_TAG)) {
EdgeImpl edge = this.factory.createEdge(edgeElement);
// need to call this to set this graph to the edge.
this.addEdge(edge);
}
indexToPointer();
}
protected void parse(JsonObject graphObject) throws GraphException{
JsonPrimitive jsonPrimitive = graphObject.getAsJsonPrimitive(GraphSchema.GRAPH_ID_TAG);
if (jsonPrimitive != null) {
this.id = jsonPrimitive.getAsString();
}
jsonPrimitive = graphObject.getAsJsonPrimitive(GraphSchema.GRAPH_NAME_TAG);
if (jsonPrimitive != null) {
this.name = jsonPrimitive.getAsString();
}
jsonPrimitive = graphObject.getAsJsonPrimitive(GraphSchema.GRAPH_DESCRIPTION_TAG);
if (jsonPrimitive != null) {
this.description = jsonPrimitive.getAsString();
}
JsonArray jArray = graphObject.getAsJsonArray(GraphSchema.NODE_TAG);
for (JsonElement jsonElement : jArray) {
addNode(this.factory.createNode((JsonObject) jsonElement));
}
jArray = graphObject.getAsJsonArray(GraphSchema.PORT_TAG);
for (JsonElement jsonElement : jArray) {
addPort(this.factory.createPort((JsonObject) jsonElement));
}
jArray = graphObject.getAsJsonArray(GraphSchema.EDGE_TAG);
for (JsonElement jsonElement : jArray) {
addEdge(this.factory.createEdge((JsonObject)jsonElement));
}
indexToPointer();
}
/**
* Adds a node.
*
* @param node
* the node to add
*/
protected void addNode(NodeImpl node) {
node.setGraph(this);
// if this is a Stream now put it at the begining
if (node instanceof StreamSourceNode) {
this.nodes.add(0, node);
} else {
this.nodes.add(node);
}
}
/**
* @param port
*/
protected void addPort(PortImpl port) {
port.setGraph(this);
this.ports.add(port);
}
/**
* Converts indexes to references. This method is called after reading the graph from an XML file.
*
* @throws GraphException
*/
protected void indexToPointer() throws GraphException {
for (NodeImpl node : this.nodes) {
node.indexToPointer();
}
for (PortImpl port : this.ports) {
port.indexToPointer();
}
for (EdgeImpl edge : this.edges) {
edge.indexToPointer();
}
}
/**
* @param edge
*/
private void addEdge(EdgeImpl edge) {
edge.setGraph(this);
this.edges.add(edge);
}
// private void createID() {
// Date date = new Date();
// SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd_HHmmss_S");
// String time = format.format(date);
//
// this.id = StringUtil.convertToJavaIdentifier(this.name) + "_" + time;
// }
/**
* @throws GraphException
*/
public void fixParameterNodes() {
// XXX fix the ports of parameter nodes for 2.6.3 or before.
for (InputNode node : GraphUtil.getNodes(this, InputNode.class)) {
DataPort oldPort = node.getOutputPort(0);
if (oldPort instanceof WSPort) {
node.getOutputPorts().remove(oldPort);
this.ports.remove(oldPort);
SystemDataPort newPort = new SystemDataPort();
this.ports.add(newPort);
newPort.setKind(Kind.DATA_OUT);
newPort.setName(oldPort.getName());
newPort.setGraph(this);
newPort.setNode(node);
newPort.createID();
node.getOutputPorts().add(newPort);
for (DataEdge edge : oldPort.getEdges()) {
edge.setFromPort(newPort);
newPort.getEdges().add(edge);
}
}
}
for (OutputNode node : GraphUtil.getNodes(this, OutputNode.class)) {
DataPort oldPort = node.getInputPort(0);
if (oldPort instanceof WSPort) {
node.getInputPorts().remove(oldPort);
this.ports.remove(oldPort);
SystemDataPort newPort = new SystemDataPort();
this.ports.add(newPort);
newPort.setKind(Kind.DATA_IN);
newPort.setName(oldPort.getName());
newPort.setGraph(this);
newPort.setNode(node);
newPort.createID();
node.getInputPorts().add(newPort);
for (DataEdge edge : oldPort.getEdges()) {
edge.setToPort(newPort);
newPort.getEdges().add(edge);
}
}
}
}
/**
* This returns the number of input Nodes, this will be useful when adding unique Id for nodes
* @return
*/
public int getCurrentInputNodeCount(){
int index=0;
for(Node node:nodes){
if(node instanceof InputNode){
index++;
}
}
return index;
}
/**
* This returns the number of input Nodes, this will be useful when adding unique Id for nodes
* @return
*/
public int getCurrentOutputNodeCount(){
int index=0;
for(Node node:nodes){
if(node instanceof OutputNode){
index++;
}
}
return index;
}
}