blob: 212f73811a6ec8f6169be602df94316c8df9cdd8 [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2007 The University of Manchester
*
* Modifications to the initial code base are copyright of their
* respective authors, or their employers as appropriate.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
******************************************************************************/
package net.sf.taverna.t2.monitor.impl;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collections;
import java.util.Date;
import java.util.Set;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeModelEvent;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import net.sf.taverna.t2.lang.observer.Observable;
import net.sf.taverna.t2.lang.observer.Observer;
import net.sf.taverna.t2.monitor.MonitorNode;
import net.sf.taverna.t2.monitor.MonitorableProperty;
import net.sf.taverna.t2.monitor.NoSuchPropertyException;
import net.sf.taverna.t2.monitor.MonitorManager.AddPropertiesMessage;
import net.sf.taverna.t2.monitor.MonitorManager.DeregisterNodeMessage;
import net.sf.taverna.t2.monitor.MonitorManager.MonitorMessage;
import net.sf.taverna.t2.monitor.MonitorManager.RegisterNodeMessage;
import org.apache.log4j.Logger;
/**
* A relatively naive Monitor interface which holds all
* state in a tree model. Use getMonitor() to get the monitor singleton, all
* workflows under a given JVM use the same instance in this implementation with
* the root node of the monitor tree corresponding to the monitor itself.
* <p>
* Internally we use a default tree model with default mutable tree nodes where
* the user object is set to instances of MonitorNode, with the exception of the
* 'true' root of the tree in which it is set to the MonitorImpl itself
*
* @author Tom Oinn
* @author Stian Soiland-Reyes
*
*/
public class MonitorTreeModel implements Observer<MonitorMessage> {
private static MonitorTreeModel instance = null;
private static Logger logger = Logger.getLogger(MonitorTreeModel.class);
/**
* Get the MonitorImpl singleton
*
* @return The MonitorImpl singleton
*/
public synchronized static MonitorTreeModel getInstance() {
if (instance == null) {
instance = new MonitorTreeModel();
}
return instance;
}
private long nodeRemovalDelay = 1000;
private DefaultTreeModel monitorTree;
private java.util.Timer nodeRemovalTimer;
/**
* Protected constructor, use singleton access {@link #getInstance()}
* instead.
*
*/
protected MonitorTreeModel() {
monitorTree = new DefaultTreeModel(new DefaultMutableTreeNode(this));
// Create the node removal timer as a daemon thread
nodeRemovalTimer = new java.util.Timer(true);
}
/**
* Returns a tree view over the monitor.
*
* @return a tree view over the monitor
*/
public JTree getJTree() {
return new AlwaysOpenJTree(monitorTree);
}
/**
* {@inheritDoc}
*/
public void notify(Observable<MonitorMessage> sender, MonitorMessage message)
throws Exception {
if (message instanceof RegisterNodeMessage) {
RegisterNodeMessage regMessage = (RegisterNodeMessage) message;
registerNode(regMessage.getWorkflowObject(), regMessage
.getOwningProcess(), regMessage.getProperties());
} else if (message instanceof DeregisterNodeMessage) {
deregisterNode(message.getOwningProcess());
} else if (message instanceof AddPropertiesMessage) {
AddPropertiesMessage addMessage = (AddPropertiesMessage) message;
addPropertiesToNode(addMessage.getOwningProcess(), addMessage
.getNewProperties());
} else {
logger.warn("Unknown message " + message + " from " + sender);
}
}
/**
* Nodes will be removed at least delayTime milliseconds after their initial
* deregistration request, this allows UI components to show nodes which
* would otherwise vanish almost instantaneously.
*
* @param delayTime
* time in milliseconds between the deregistration request and
* attempt to actually remove the node in question
*/
public void setNodeRemovalDelay(long delayTime) {
nodeRemovalDelay = delayTime;
}
/**
* Very simple UI!
*/
public void showMonitorFrame() {
final JTree tree = new AlwaysOpenJTree(monitorTree);
final JScrollPane jsp = new JScrollPane(tree);
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(jsp);
frame.pack();
frame.setVisible(true);
new javax.swing.Timer(500, new ActionListener() {
public void actionPerformed(ActionEvent ae) {
jsp.repaint();
}
}).start();
}
/**
* Return the node pointed to by the first 'limit' number of elements in the
* owning process string array. If limit is -1 then use owningProcess.length
*
* @param owningProcess
* @param limit
* @return
*/
protected DefaultMutableTreeNode nodeAtProcessPath(String[] owningProcess,
int limit) throws IndexOutOfBoundsException {
if (limit == -1) {
limit = owningProcess.length;
}
DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) monitorTree
.getRoot();
for (int index = 0; index < limit && index < owningProcess.length; index++) {
boolean found = false;
for (int childIndex = 0; childIndex < monitorTree
.getChildCount(currentNode)
&& !found; childIndex++) {
DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) monitorTree
.getChild(currentNode, childIndex);
MonitorNode childMonitorNode = (MonitorNode) childNode
.getUserObject();
if (childMonitorNode.getOwningProcess()[index]
.equals(owningProcess[index])) {
currentNode = childNode;
found = true;
// break;
}
}
if (!found) {
throw new IndexOutOfBoundsException(
"Cannot locate node with process ID "
+ printProcess(owningProcess));
}
}
return currentNode;
}
protected String printProcess(String[] process) {
StringBuffer sb = new StringBuffer();
for (String part : process) {
sb.append("{" + part + "}");
}
return sb.toString();
}
/**
* Inject properties into an existing node
*/
protected void addPropertiesToNode(String[] owningProcess,
Set<MonitorableProperty<?>> newProperties) {
try {
DefaultMutableTreeNode node = nodeAtProcessPath(owningProcess, -1);
MonitorNode mn = (MonitorNode) node.getUserObject();
for (MonitorableProperty<?> prop : newProperties) {
mn.addMonitorableProperty(prop);
}
} catch (IndexOutOfBoundsException ioobe) {
// Fail silently here, the node wasn't found in the state tree
logger.warn("Could not add properties to unknown node "
+ printProcess(owningProcess));
}
}
/**
* Request the removal of the specified node from the monitor tree. In this
* particular case the removal task will be added to a timer and executed at
* some (slightly) later time as determined by the removalDelay property.
*/
protected void deregisterNode(String[] owningProcess) {
// logger.debug("Remove node @" +
// printProcess(owningProcess));
final DefaultMutableTreeNode nodeToRemove = nodeAtProcessPath(
owningProcess, -1);
((MonitorNodeImpl) nodeToRemove.getUserObject()).expire();
nodeRemovalTimer.schedule(new java.util.TimerTask() {
@Override
public void run() {
synchronized (monitorTree) {
monitorTree.removeNodeFromParent(nodeToRemove);
}
}
}, getNodeRemovalDelay());
}
/**
* Create a new node in the monitor
*/
protected void registerNode(final Object workflowObject,
final String[] owningProcess,
final Set<MonitorableProperty<?>> properties) {
// logger.debug("Registering node " + printProcess(owningProcess));
// Create a new MonitorNode
final DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(
new MonitorNodeImpl(workflowObject, owningProcess, properties));
synchronized (monitorTree) {
final MutableTreeNode parentNode = nodeAtProcessPath(owningProcess,
owningProcess.length - 1);
monitorTree.insertNodeInto(newNode, parentNode, monitorTree
.getChildCount(parentNode));
}
}
class AlwaysOpenJTree extends JTree {
private static final long serialVersionUID = -3769998854485605447L;
public AlwaysOpenJTree(TreeModel newModel) {
super(newModel);
setRowHeight(18);
setLargeModel(true);
setEditable(false);
setExpandsSelectedPaths(false);
setDragEnabled(false);
setScrollsOnExpand(false);
setSelectionModel(EmptySelectionModel.sharedInstance());
setCellRenderer(new CellRenderer());
}
@Override
public void setModel(TreeModel model) {
if (treeModel == model)
return;
if (treeModelListener == null)
treeModelListener = new TreeModelListener();
if (model != null) {
model.addTreeModelListener(treeModelListener);
}
TreeModel oldValue = treeModel;
treeModel = model;
firePropertyChange(TREE_MODEL_PROPERTY, oldValue, model);
}
protected class CellRenderer extends DefaultTreeCellRenderer {
private static final long serialVersionUID = 7106767124654545039L;
@Override
public Component getTreeCellRendererComponent(JTree tree,
Object value, boolean sel, boolean expanded,
boolean leaf, int row, boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel,
expanded, leaf, row, hasFocus);
if (value instanceof DefaultMutableTreeNode) {
Object o = ((DefaultMutableTreeNode) value)
.getUserObject();
if (o instanceof MonitorNode) {
MonitorNode mn = (MonitorNode) o;
if (mn.hasExpired()) {
setEnabled(false);
}
}
}
return this;
}
}
protected class TreeModelListener extends TreeModelHandler {
@Override
public void treeNodesInserted(final TreeModelEvent ev) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
TreePath path = ev.getTreePath();
setExpandedState(path, true);
fireTreeExpanded(path);
}
});
}
@Override
public void treeStructureChanged(final TreeModelEvent ev) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
TreePath path = ev.getTreePath();
setExpandedState(path, true);
fireTreeExpanded(path);
}
});
}
}
}
class MonitorNodeImpl implements MonitorNode {
private boolean expired = false;
private String[] owningProcess;
private Set<MonitorableProperty<?>> properties;
private Object workflowObject;
Date creationDate = new Date();
MonitorNodeImpl(Object workflowObject, String[] owningProcess,
Set<MonitorableProperty<?>> properties) {
this.properties = properties;
this.workflowObject = workflowObject;
this.owningProcess = owningProcess;
}
public void addMonitorableProperty(MonitorableProperty<?> newProperty) {
properties.add(newProperty);
}
public void expire() {
expired = true;
}
public Date getCreationDate() {
return creationDate;
}
public String[] getOwningProcess() {
return owningProcess;
}
/**
* Return an unmodifiable copy of the property set
*/
public Set<? extends MonitorableProperty<?>> getProperties() {
return Collections.unmodifiableSet(properties);
}
public Object getWorkflowObject() {
return workflowObject;
}
public boolean hasExpired() {
return this.expired;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(getWorkflowObject().getClass().getSimpleName());
sb.append(", ");
sb.append(owningProcess[owningProcess.length - 1]);
sb.append(" : ");
for (MonitorableProperty<?> prop : getProperties()) {
int i = 0;
for (String nameElement : prop.getName()) {
sb.append(nameElement);
i++;
if (i < prop.getName().length) {
sb.append(".");
}
}
sb.append("=");
try {
sb.append(prop.getValue().toString());
} catch (NoSuchPropertyException nspe) {
sb.append("EXPIRED");
}
sb.append(" ");
}
return sb.toString();
}
}
public long getNodeRemovalDelay() {
return nodeRemovalDelay;
}
protected DefaultTreeModel getMonitorTree() {
return monitorTree;
}
}