/**
*
* 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.xbaya.ui.monitor;
import org.apache.airavata.model.messaging.event.MessageType;
import org.apache.airavata.model.experiment.WorkflowNodeState;
import org.apache.airavata.workflow.model.graph.EPRPort;
import org.apache.airavata.workflow.model.graph.Edge;
import org.apache.airavata.workflow.model.graph.Graph;
import org.apache.airavata.workflow.model.graph.Node;
import org.apache.airavata.workflow.model.graph.Node.NodeExecutionState;
import org.apache.airavata.workflow.model.graph.Port;
import org.apache.airavata.workflow.model.graph.impl.NodeImpl;
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.util.GraphUtil;
import org.apache.airavata.workflow.model.graph.ws.WSGraph;
import org.apache.airavata.workflow.model.wf.Workflow;
import org.apache.airavata.xbaya.graph.controller.NodeController;
import org.apache.airavata.xbaya.messaging.EventData;
import org.apache.airavata.xbaya.messaging.EventDataRepository;
import org.apache.airavata.xbaya.ui.XBayaGUI;
import org.apache.airavata.xbaya.ui.graph.GraphCanvas;
import org.apache.airavata.xbaya.ui.graph.NodeGUI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class MonitorEventHandler implements ChangeListener {
/**
* The state of a node
*/
public static enum NodeState {
/**
* FINISHED
*/
FINISHED(Color.GRAY),
/**
* EXECUTING
*/
EXECUTING(Color.GREEN),
/**
* FAILED
*/
FAILED(Color.RED),
/**
* DEFAULT COLOR
*/
DEFAULT(NodeGUI.DEFAULT_BODY_COLOR);
/**
* color
*/
final public Color color;
private NodeState(Color color) {
this.color = color;
}
}
private static Logger logger = LoggerFactory.getLogger(MonitorEventHandler.class);
private XBayaGUI xbayaGUI;
private int sliderValue;
private Collection<URI> incorrectWorkflowIDs;
private Collection<URI> triedWorkflowIDs;
private Map<Node, LinkedList<ResourcePaintable>> resourcePaintableMap;
// private WorkflowStatusUpdater workflowStatusUpdater;
// private WorkflowNodeStatusUpdater workflowNodeStatusUpdater;
/**
* Model MonitorEventHandler
*
* @param xbayaGUI
*/
public MonitorEventHandler(XBayaGUI xbayaGUI) {
this.xbayaGUI = xbayaGUI;
this.incorrectWorkflowIDs = Collections.synchronizedSet(new HashSet<URI>());
this.triedWorkflowIDs = Collections.synchronizedSet(new HashSet<URI>());
this.resourcePaintableMap = new HashMap<Node, LinkedList<ResourcePaintable>>();
// try {
// if (this.getClass().getClassLoader().getResource("airavata-server.properties") != null) {
// this.workflowNodeStatusUpdater =
// new WorkflowNodeStatusUpdater(XBayaUtil.getExperimentCatalog(this.getClass().getClassLoader().getResource("airavata-server.properties")));
// this.workflowStatusUpdater =
// new WorkflowStatusUpdater(XBayaUtil.getExperimentCatalog(this.getClass().getClassLoader().getResource("airavata-server.properties")));
// }
// } catch (IOException e) {
// e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
// } catch (RepositoryException e) {
// e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
// } catch (URISyntaxException e) {
// e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
// }
}
/**
* @see javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent)
*/
public void stateChanged(ChangeEvent event) {
try {
Object source = event.getSource();
if (source instanceof EventDataRepository) {
handleChange((EventDataRepository) source);
}else if (source instanceof EventData) {
EventData eventData = (EventData) source;
if (eventData.getType() == MessageType.WORKFLOWNODE && eventData.getMessageId().startsWith("NODE")) {
handleEvent(eventData, true);
}
}else if (source instanceof String) {
handleNewWorkflowStartEvent((String) source);
}
} catch (RuntimeException e) {
// Don't want to pop up an error dialog every time XBaya received an
// ill-formatted notification.
logger.error(e.getMessage(), e);
}
}
private void handleNewWorkflowStartEvent(String workflowName) {
Workflow workflow;
for (GraphCanvas graphCanvas : this.xbayaGUI.getGraphCanvases()) {
workflow = graphCanvas.getWorkflow();
if (workflow.getName().equals(workflowName)) {
resetAllNode(workflow);
}
}
}
private void resetAllNode(Workflow workflow) {
Graph graph = workflow.getGraph();
for (Node node : graph.getNodes()) {
resetNode(node);
}
}
/**
* @param model
*/
private void handleChange(EventDataRepository model) {
int newValue = model.getValue();
if (model.getEventSize() == 0) {
// The monitor was reset.
resetAll();
} else if (newValue > this.sliderValue) {
// 3 -> 5
// 3, 4
for (int i = this.sliderValue; i < newValue; i++) {
handleEvent(model.getEvent(i), true);
}
} else if (newValue < this.sliderValue) {
// 5 -> 3
// 4, 3
for (int i = this.sliderValue - 1; i >= newValue; i--) {
handleEvent(model.getEvent(i), false);
}
}
this.sliderValue = newValue;
// Repaints only the active canvas.
this.xbayaGUI.getGraphCanvas().repaint();
}
private void handleEvent(EventData event, boolean forward) {
// EventType type = event.getType();
//todo currrently we do not set the workflowID properly its just node ID
// URI workflowID = event.getWorkflowID();
List<GraphCanvas> graphCanvases = this.xbayaGUI.getGraphCanvases();
for (GraphCanvas graphCanvas : graphCanvases) {
Workflow workflow = graphCanvas.getWorkflow();
// URI instanceID = workflow.getGPELInstanceID();
// if (instanceID == null) {
// If the workflow doesn't have an instance ID, it's a template.
// We handle it so that users can use a workflow template to
// monitor a workflow too.
// This is also needed in the case of jython workflow.
handleEvent(event, forward, workflow.getGraph());
graphCanvas.repaint();
// } else if (instanceID.equals(workflowID)) {
// This is the regular case.
// found = true;
// handleEvent(event, forward, workflow.getGraph());
// } else if (null != workflowID
// && -1 != WSDLUtil.findWorkflowName(workflowID).indexOf(WSDLUtil.findWorkflowName(instanceID))) {
// handleEvent(event, WSDLUtil.findWorkflowName(workflowID), workflow.getGraph());
// }
}
// // Load a sub-workflow.
// if (type == MonitorUtil.EventType.WORKFLOW_INITIALIZED) {
// if (forward) {
// // Check if the workflow instance is already open.
// for (GraphCanvas graphCanvas : graphCanvases) {
// Workflow workflow = graphCanvas.getWorkflow();
// URI instanceID = workflow.getGPELInstanceID();
// if (workflowID.equals(instanceID)) {
// // The workflow instance is already loaded.
// return;
// }
// }
// loadWorkflow(workflowID);
// } else {
// // Don't need to close the workflow when it's opened.
// }
// }
//
// if (found == false && workflowID != null) {
// // Loads the workflow instance ID in case a user started to monitor
// // in the middle.
// loadWorkflow(workflowID);
// }
//
// /*
// * Handle resource mapping message which contains resource from Amazon EC2 Since workflowID (workflowName) from
// * message and instanceID do not equal, so we have to handle it explicitly
// */
// if (type == EventType.RESOURCE_MAPPING && event.getMessage().contains("i-")) {
// String nodeID = event.getNodeID();
// for (GraphCanvas graphCanvas : graphCanvases) {
// Node node = graphCanvas.getWorkflow().getGraph().getNode(nodeID);
// if (node != null) {
// ControlPort control = node.getControlInPort();
// if (control != null) {
// Node fromNode = control.getFromNode();
// if (fromNode instanceof InstanceNode) {
// InstanceNode ec2Node = (InstanceNode) fromNode;
//
// /*
// * parse message and set output to InstanceNode
// */
// int start = event.getMessage().indexOf("i-");
// String instanceId = event.getMessage().substring(start, start + 10);
// ec2Node.setOutputInstanceId(instanceId);
//
// // make this node to not start a new instance
// ec2Node.setStartNewInstance(false);
// ec2Node.setInstanceId(instanceId);
// ec2Node.setAmiId(null);
// }
// }
// }
// }
// }
// TODO There is a possibility that XBaya misses to handle some
// notification while a workflow is being loaded. Create a thread for
// each workflow ID to handle notifications.
}
/**
* @param graph
* @return
*/
private LinkedList<InputNode> getInputNodes(WSGraph graph) {
List<NodeImpl> nodes = graph.getNodes();
LinkedList<InputNode> inputNodes = new LinkedList<InputNode>();
for (NodeImpl nodeImpl : nodes) {
if (nodeImpl instanceof InputNode) {
inputNodes.add((InputNode) nodeImpl);
}
}
return inputNodes;
}
private LinkedList<OutputNode> getOutputNodes(WSGraph graph) {
List<NodeImpl> nodes = graph.getNodes();
LinkedList<OutputNode> outputNodes = new LinkedList<OutputNode>();
for (NodeImpl nodeImpl : nodes) {
if (nodeImpl instanceof OutputNode) {
outputNodes.add((OutputNode) nodeImpl);
}
}
return outputNodes;
}
/**
* @param event
* @param forward
* @param graph
*/
private void handleEvent(EventData event, boolean forward, Graph graph) {
// EventType type = event.getType();
String nodeID = event.getWorkflowNodeId();
Node node = graph.getNode(nodeID);
if (node != null){
if (event.getStatus().equals(WorkflowNodeState.INVOKED.toString())) {
invokeNode(node);
// workflowStarted(graph, forward);
// workflowStatusUpdater.workflowStarted(event.getExperimentID());
} else if (event.getStatus().equals(WorkflowNodeState.COMPLETED.toString())) {
nodeFinished(node, true);
// workflowFinished(graph, forward);
// workflowStatusUpdater.workflowFinished(event.getExperimentID());
} else if (event.getStatus().equals(WorkflowNodeState.EXECUTING.toString())) {
nodeStarted(node, forward);
// workflowNodeStatusUpdater.workflowStarted(event.getExperimentID(), event.getNodeID());
} else {
// Ignore the rest.
}
}
}
/**
* @param workflowInstanceID
*/
private void loadWorkflow(final URI workflowInstanceID) {
// To avoid to load a same workflow twice.
if (this.triedWorkflowIDs.contains(workflowInstanceID)) {
return;
}
this.triedWorkflowIDs.add(workflowInstanceID);
new Thread() {
@Override
public void run() {
loadWorkflowInThread(workflowInstanceID);
}
}.start();
}
private void loadWorkflowInThread(URI workflowInstanceID) {
try {
if (this.incorrectWorkflowIDs.contains(workflowInstanceID)) {
// Do not try to load a workflow that failed before.
return;
}
//There is not workflow client assigned in the engine. thus the following code is commented
// WorkflowClient client = this.engine.getWorkflowClient();
// Workflow loadedWorkflow = client.load(workflowInstanceID, WorkflowType.INSTANCE);
// GraphCanvas canvas = this.xbayaGUI.newGraphCanvas(true);
// canvas.setWorkflow(loadedWorkflow);
// } catch (GraphException e) {
// this.incorrectWorkflowIDs.add(workflowInstanceID);
// logger.error(e.getMessage(), e);
// } catch (WorkflowEngineException e) {
// this.incorrectWorkflowIDs.add(workflowInstanceID);
// logger.error(e.getMessage(), e);
// } catch (ComponentException e) {
// this.incorrectWorkflowIDs.add(workflowInstanceID);
// logger.error(e.getMessage(), e);
} catch (RuntimeException e) {
this.incorrectWorkflowIDs.add(workflowInstanceID);
logger.error(e.getMessage(), e);
}
}
private void workflowStarted(Graph graph, boolean forward) {
for (InputNode node : GraphUtil.getInputNodes(graph)) {
if (forward) {
finishNode(node);
} else {
resetNode(node);
}
}
}
private void workflowFinished(Graph graph, boolean forward) {
for (OutputNode node : GraphUtil.getOutputNodes(graph)) {
if (forward) {
finishNode(node);
finishPredecessorNodes(node);
} else {
resetNode(node);
}
}
}
private void nodeStarted(Node node, boolean forward) {
if (forward) {
if (node.getState() != NodeExecutionState.FINISHED) {
executeNode(node);
finishPredecessorNodes(node);
}
} else {
resetNode(node);
}
}
private void nodeFinished(Node node, boolean forward) {
if (forward) {
finishNode(node);
finishPredecessorNodes(node);
} else {
executeNode(node);
}
}
private void nodeFailed(Node node, boolean forward) {
if (forward) {
failNode(node);
finishPredecessorNodes(node);
} else {
executeNode(node);
}
}
private void resetAll() {
List<GraphCanvas> graphCanvases = this.xbayaGUI.getGraphCanvases();
for (GraphCanvas graphCanvas : graphCanvases) {
Graph graph = graphCanvas.getGraph();
for (Node node : graph.getNodes()) {
resetNode(node);
}
}
}
private void executeNode(Node node) {
node.setState(NodeExecutionState.EXECUTING);
}
private void invokeNode(Node node) {
node.setState(NodeExecutionState.WAITING);
}
private void finishNode(Node node) {
if (!NodeExecutionState.FAILED.equals(node.getState())) {
node.setState(NodeExecutionState.FINISHED);
}
}
private void failNode(Node node) {
node.setState(NodeExecutionState.FAILED);
}
private void resetNode(Node node) {
node.setState(NodeExecutionState.WAITING);
NodeController.getGUI(node).resetTokens();
}
/**
* Make preceding nodes done. This helps the monitoring GUI when a user subscribes from the middle of the workflow
* execution.
*
* @param node
*/
private void finishPredecessorNodes(Node node) {
for (Port inputPort : node.getInputPorts()) {
for (Edge edge : inputPort.getEdges()) {
Port fromPort = edge.getFromPort();
if (!(fromPort instanceof EPRPort)) {
Node fromNode = fromPort.getNode();
finishNode(fromNode);
finishPredecessorNodes(fromNode);
}
}
}
Port controlInPort = node.getControlInPort();
if (controlInPort != null) {
for (Node fromNode : controlInPort.getFromNodes()) {
finishNode(fromNode);
finishPredecessorNodes(fromNode);
}
}
}
}