blob: 33f92d688c8a267c303e366777e9b6e3f68a216f [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.workbench.views.results.processor;
import static java.awt.BorderLayout.CENTER;
import static java.awt.BorderLayout.EAST;
import static java.awt.BorderLayout.NORTH;
import static java.awt.BorderLayout.SOUTH;
import static java.awt.BorderLayout.WEST;
import static java.awt.Color.RED;
import static java.awt.GridBagConstraints.BOTH;
import static java.awt.GridBagConstraints.NONE;
import static java.util.Collections.emptyList;
import static java.util.Collections.sort;
import static java.util.Collections.synchronizedSet;
import static javax.swing.BorderFactory.createEmptyBorder;
import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
import static javax.swing.SwingUtilities.invokeLater;
import static javax.swing.SwingUtilities.isEventDispatchThread;
import static javax.swing.border.EtchedBorder.LOWERED;
import static javax.swing.tree.TreeSelectionModel.SINGLE_TREE_SELECTION;
import static net.sf.taverna.t2.workbench.MainWindow.getMainWindow;
import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.closeIcon;
import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.inputIcon;
import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.outputIcon;
import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.saveAllIcon;
import static net.sf.taverna.t2.workbench.views.results.processor.IterationTreeNode.ErrorState.INPUT_ERRORS;
import static net.sf.taverna.t2.workbench.views.results.processor.IterationTreeNode.ErrorState.OUTPUT_ERRORS;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.nio.file.Path;
import java.sql.Timestamp;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTree;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath;
import net.sf.taverna.t2.facade.WorkflowInstanceFacade;
import net.sf.taverna.t2.facade.WorkflowInstanceFacade.State;
import net.sf.taverna.t2.lang.ui.DialogTextArea;
import net.sf.taverna.t2.provenance.ProvenanceConnectorFactory;
import net.sf.taverna.t2.provenance.api.ProvenanceAccess;
import net.sf.taverna.t2.provenance.lineageservice.utils.Port;
import net.sf.taverna.t2.provenance.lineageservice.utils.ProcessorEnactment;
import net.sf.taverna.t2.reference.ReferenceService;
import net.sf.taverna.t2.reference.T2Reference;
import net.sf.taverna.t2.renderers.RendererRegistry;
import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
import net.sf.taverna.t2.workbench.ui.SwingWorkerCompletionWaiter;
import net.sf.taverna.t2.workbench.views.results.processor.FilteredIterationTreeModel.FilterType;
import net.sf.taverna.t2.workbench.views.results.processor.IterationTreeNode.ErrorState;
import net.sf.taverna.t2.workbench.views.results.saveactions.SaveAllResultsSPI;
import net.sf.taverna.t2.workbench.views.results.saveactions.SaveIndividualResultSPI;
import net.sf.taverna.t2.workflowmodel.Dataflow;
import net.sf.taverna.t2.workflowmodel.Processor;
import net.sf.taverna.t2.workflowmodel.ProcessorInputPort;
import net.sf.taverna.t2.workflowmodel.ProcessorOutputPort;
import net.sf.taverna.t2.workflowmodel.utils.Tools;
import org.apache.log4j.Logger;
import uk.org.taverna.configuration.database.DatabaseConfiguration;
/**
* A component that contains a tabbed pane for displaying inputs and outputs of a processor (i.e.
* intermediate results for a workflow run).
*<p>
*FIXME Needs deleting or converting to DataBundles
* @author Alex Nenadic
*
*/
@SuppressWarnings("serial")
public class ProcessorResultsComponent extends JPanel {
private static final Logger logger = Logger.getLogger(ProcessorResultsComponent.class);
private static final SimpleDateFormat ISO_8601 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final String HOURS = "h";
private static final String MINUTES = "m";
private static final String SECONDS = "s";
private static final String MILLISECONDS = "ms";
/**
* JSplitPane that contains the invocation list for the processor on the
* left and a tabbed pane with processors ports on the right.
*/
private JSplitPane splitPane;
/** Tree containing enactments (invocations) of the processor.*/
protected JTree processorEnactmentsTree;
/**
* Tabbed pane - each tab contains a processor input/outputs data/results
* tree and a RenderedProcessorResultComponent, which in turn contains the
* currently selected node rendered according to its MIME type.
*/
private JTabbedPane tabbedPane;
/** Panel containing the title*/
private JPanel titlePanel;
private Processor processor;
@SuppressWarnings("unused")
private Dataflow dataflow;
private String runId;
@SuppressWarnings("unused")
private ReferenceService referenceService;
private WorkflowInstanceFacade facade; // in the case this is a fresh run
boolean resultsUpdateNeeded = false;
/** Enactments received for this processor */
private Set<ProcessorEnactment> enactmentsGotSoFar = synchronizedSet(new HashSet<ProcessorEnactment>());
private Set<String> enactmentIdsGotSoFar = synchronizedSet(new HashSet<String>());
private Map<String, ProcessorPortResultsViewTab> inputPortTabMap = new ConcurrentHashMap<>();
private Map<String, ProcessorPortResultsViewTab> outputPortTabMap = new ConcurrentHashMap<>();
/** All data for intermediate results is pulled from provenance.*/
private ProvenanceAccess provenanceAccess;
private ProcessorEnactmentsTreeModel processorEnactmentsTreeModel;
private FilteredIterationTreeModel filteredTreeModel;
/**
* Map: enactment -> (port, t2Ref, tree).
* <p>
* Each enactment is mapped to a list of 3-element lists. The 3-element list
* contains processor input/output port, t2ref to data consumed/produced on
* that port and tree view of the data. Tree is only created on demand -
* i.e. when user selects a particular enactment and a specific port.
*/
protected Map<ProcessorEnactment, List<List<Object>>> enactmentsToInputPortData = new ConcurrentHashMap<>();
protected Map<ProcessorEnactment, List<List<Object>>> enactmentsToOutputPortData = new ConcurrentHashMap<>();
protected Set<ProcessorEnactment> enactmentsWithErrorInputs = synchronizedSet(new HashSet<ProcessorEnactment>());
protected Set<ProcessorEnactment> enactmentsWithErrorOutputs = synchronizedSet(new HashSet<ProcessorEnactment>());
private JLabel iterationLabel;
/**
* List of all existing 'save results' actions, each one can save results in
* a different format
*/
private final List<SaveAllResultsSPI> saveActions;
private JButton saveAllButton;
private String processorId = null;
private List<Processor> processorsPath;
private ProcessorEnactmentsTreeNode procEnactmentTreeNode = null;
private final RendererRegistry rendererRegistry;
private final List<SaveIndividualResultSPI> saveIndividualActions;
public ProcessorResultsComponent(Processor processor, Dataflow dataflow,
String runId, ReferenceService referenceService,
RendererRegistry rendererRegistry,
List<SaveAllResultsSPI> saveActions,
List<SaveIndividualResultSPI> saveIndividualActions,
List<ProvenanceConnectorFactory> provenanceConnectorFactories,
DatabaseConfiguration databaseConfiguration) {
super(new BorderLayout());
this.processor = processor;
this.rendererRegistry = rendererRegistry;
this.saveActions = saveActions;
this.saveIndividualActions = saveIndividualActions;
this.processorsPath = Tools.getNestedPathForProcessor(processor,
dataflow);
this.dataflow = dataflow;
this.runId = runId;
this.referenceService = referenceService;
this.facade = null;
provenanceAccess = new ProvenanceAccess(
databaseConfiguration.getConnectorType(),
provenanceConnectorFactories);
initComponents();
}
public ProcessorResultsComponent(WorkflowInstanceFacade facade, Processor processor,
Dataflow dataflow, String runId, ReferenceService referenceService,
RendererRegistry rendererRegistry, List<SaveAllResultsSPI> saveActions,
List<SaveIndividualResultSPI> saveIndividualActions,
List<ProvenanceConnectorFactory> provenanceConnectorFactories,
DatabaseConfiguration databaseConfiguration) {
super(new BorderLayout());
this.processor = processor;
this.rendererRegistry = rendererRegistry;
this.saveActions = saveActions;
this.saveIndividualActions = saveIndividualActions;
this.processorsPath = Tools.getNestedPathForProcessor(processor, dataflow);
this.dataflow = dataflow;
this.runId = runId;
this.referenceService = referenceService;
this.facade = facade;
provenanceAccess = new ProvenanceAccess(databaseConfiguration.getConnectorType(), provenanceConnectorFactories);
/**
* Is this still a running wf - do we need to periodically check with
* provenance for new results?
*/
resultsUpdateNeeded = !(facade.getState().equals(State.cancelled) || facade
.getState().equals(State.completed));
initComponents();
}
public void initComponents() {
setBorder(new EtchedBorder());
titlePanel = new JPanel(new BorderLayout());
titlePanel.setBorder(new EmptyBorder(5, 0, 5, 0));
titlePanel.add(new JLabel("Intermediate results for service: " + processor.getLocalName()),
WEST);
String title = "<html><body>Intermediate values for the service <b>"
+ processor.getLocalName() + "</b></body></html>";
JLabel tableLabel = new JLabel(title);
titlePanel.add(tableLabel, WEST);
iterationLabel = new JLabel();
int spacing = iterationLabel.getFontMetrics(iterationLabel.getFont()).charWidth(' ');
iterationLabel.setBorder(createEmptyBorder(0, spacing * 5, 0, 0));
titlePanel.add(iterationLabel, CENTER);
saveAllButton = new JButton(new SaveAllAction("Save iteration values", this));
saveAllButton.setEnabled(false);
titlePanel.add(saveAllButton, EAST);
add(titlePanel, NORTH);
tabbedPane = new JTabbedPane();
// Create enactment to (port, t2ref, tree) lists maps.
enactmentsToInputPortData = new HashMap<>();
enactmentsToOutputPortData = new HashMap<>();
// Processor input ports
List<ProcessorInputPort> processorInputPorts = new ArrayList<>(
processor.getInputPorts());
sort(processorInputPorts, new Comparator<ProcessorInputPort>() {
@Override
public int compare(ProcessorInputPort o1, ProcessorInputPort o2) {
return o1.getName().compareTo(o2.getName());
}
});
for (ProcessorInputPort processorInputPort : processorInputPorts) {
String portName = processorInputPort.getName();
ProcessorPortResultsViewTab resultTab = new ProcessorPortResultsViewTab(
portName, rendererRegistry, saveIndividualActions);
resultTab.setIsOutputPortTab(false);
inputPortTabMap.put(portName, resultTab);
tabbedPane.addTab(portName, inputIcon, resultTab, "Input port "
+ portName);
}
// Processor output ports
List<ProcessorOutputPort> processorOutputPorts = new ArrayList<>(
processor.getOutputPorts());
sort(processorOutputPorts, new Comparator<ProcessorOutputPort>() {
@Override
public int compare(ProcessorOutputPort o1, ProcessorOutputPort o2) {
return o1.getName().compareTo(o2.getName());
}
});
for (ProcessorOutputPort processorOutputPort : processorOutputPorts) {
String portName = processorOutputPort.getName();
ProcessorPortResultsViewTab resultTab = new ProcessorPortResultsViewTab(
portName, rendererRegistry, saveIndividualActions);
resultTab.setIsOutputPortTab(true);
outputPortTabMap.put(portName, resultTab);
tabbedPane.addTab(portName, outputIcon, resultTab, "Output port "
+ portName);
}
processorEnactmentsTreeModel = new ProcessorEnactmentsTreeModel(enactmentsGotSoFar,
enactmentsWithErrorInputs, enactmentsWithErrorOutputs);
filteredTreeModel = new FilteredIterationTreeModel(processorEnactmentsTreeModel);
processorEnactmentsTree = new JTree(filteredTreeModel);
processorEnactmentsTree.setRootVisible(false);
processorEnactmentsTree.setShowsRootHandles(true);
processorEnactmentsTree.getSelectionModel().setSelectionMode(
SINGLE_TREE_SELECTION);
// Start listening for selections in the enactments tree
processorEnactmentsTree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
// Change the result for the selected enactment in the
// current tab
setDataTreeForResultTab();
}
});
processorEnactmentsTree.setCellRenderer(new DefaultTreeCellRenderer() {
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row,
hasFocus);
if (value instanceof IterationTreeNode) {
IterationTreeNode iterationTreeNode = (IterationTreeNode) value;
ErrorState errorState = iterationTreeNode.getErrorState();
if (errorState.equals(OUTPUT_ERRORS))
setForeground(RED);
else if (errorState.equals(INPUT_ERRORS))
setForeground(new Color(0xdd, 0xa7, 0x00));
}
return this;
}
});
// Register a tab change listener
tabbedPane.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent evt) {
setDataTreeForResultTab();
}
});
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
splitPane.setBottomComponent(tabbedPane);
final JComboBox<FilterType> filterChoiceBox = new JComboBox<>(
new FilterType[] { FilterType.ALL, FilterType.RESULTS,
FilterType.ERRORS, FilterType.SKIPPED });
filterChoiceBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
filteredTreeModel.setFilter((FilterType) filterChoiceBox.getSelectedItem());
ProcessorResultsComponent.this.updateTree();
}
});
filterChoiceBox.setSelectedIndex(0);
JPanel enactmentsTreePanel = new JPanel(new BorderLayout());
JPanel enactmentsComboPanel = new JPanel(new BorderLayout());
enactmentsComboPanel.add(filterChoiceBox, BorderLayout.WEST);
enactmentsTreePanel.add(enactmentsComboPanel, NORTH);
enactmentsTreePanel.add(new JScrollPane(processorEnactmentsTree,
VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED),
CENTER);
splitPane.setTopComponent(enactmentsTreePanel);
add(splitPane, CENTER);
resultsUpdateNeeded = true;
update();
}
public static String formatMilliseconds(long timeInMiliseconds) {
double timeInSeconds;
if (timeInMiliseconds < 1000)
return timeInMiliseconds + " " + MILLISECONDS;
NumberFormat numberFormat = NumberFormat.getNumberInstance();
numberFormat.setMaximumFractionDigits(1);
numberFormat.setMinimumFractionDigits(1);
timeInSeconds = timeInMiliseconds / 1000.0;
if (timeInSeconds < 60)
return numberFormat.format(timeInSeconds) + " " + SECONDS;
double timeInMinutes = timeInSeconds / 60.0;
if (timeInMinutes < 60)
return numberFormat.format(timeInMinutes) + " " + MINUTES;
double timeInHours = timeInMinutes / 60.0;
return numberFormat.format(timeInHours) + " " + HOURS;
}
private void setDataTreeForResultTab() {
final ProcessorPortResultsViewTab selectedResultTab = (ProcessorPortResultsViewTab) tabbedPane
.getSelectedComponent();
if (processorEnactmentsTree.getSelectionModel().isSelectionEmpty()) {
disableResultTabForNode(selectedResultTab, null);
return;
}
TreePath selectedPath = processorEnactmentsTree.getSelectionModel()
.getSelectionPath();
Object lastPathComponent = selectedPath.getLastPathComponent();
if (!(lastPathComponent instanceof ProcessorEnactmentsTreeNode)) {
// Just an IterationTreeNode along the way, no data to show
disableResultTabForNode(selectedResultTab,
(DefaultMutableTreeNode) lastPathComponent);
return;
}
procEnactmentTreeNode = (ProcessorEnactmentsTreeNode) lastPathComponent;
ProcessorEnactment processorEnactment = (ProcessorEnactment) procEnactmentTreeNode
.getUserObject();
if (!processorEnactment.getProcessorId().equals(processorId)) {
/*
* It's not our processor, must be a nested workflow iteration,
* which we should not show
*/
disableResultTabForNode(selectedResultTab, procEnactmentTreeNode);
return;
}
// Update iterationLabel
StringBuilder iterationLabelText = labelForProcEnactment(procEnactmentTreeNode,
processorEnactment);
iterationLabel.setText(iterationLabelText.toString());
saveAllButton.setEnabled(true);
Map<ProcessorEnactment, List<List<Object>>> map;
if (selectedResultTab.getIsOutputPortTab()) // output port tab
map = enactmentsToOutputPortData;
else // input port tab
map = enactmentsToInputPortData;
List<List<Object>> listOfListsOfPortData = map.get(processorEnactment);
if (listOfListsOfPortData == null)
listOfListsOfPortData = emptyList();
JTree tree = null;
/*
* Get the tree for this port and this enactment and show it on results
* tab
*/
for (List<Object> listOfPortData : listOfListsOfPortData)
// Find data in the map for this port
if (selectedResultTab.getPortName().equals(
((Port) listOfPortData.get(0)).getPortName())) {
// list.get(0) contains the port
// list.get(1) contains the t2Ref to data
// list.get(2) contains the tree
if (listOfPortData.get(2) == null)
// tree has not been created yet
tree = createTreeForPort(selectedResultTab,
processorEnactment, map, listOfListsOfPortData,
listOfPortData);
else
tree = updateTreeForPort(selectedResultTab, listOfPortData);
break;
}
// Show the tree
selectedResultTab.setResultsTree(tree);
}
private JTree createTreeForPort(ProcessorPortResultsViewTab selectedTab,
ProcessorEnactment enactment,
Map<ProcessorEnactment, List<List<Object>>> map,
List<List<Object>> listOfPortDataLists, List<Object> portDataList) {
// Clear previously shown rendered result, if any
RenderedProcessorResultComponent renderedResultComponent = selectedTab
.getRenderedResultComponent();
renderedResultComponent.clearResult();
// Create a tree for this data
ProcessorResultsTreeModel treeModel = new ProcessorResultsTreeModel(
(Path) portDataList.get(1));
JTree tree = new JTree(new FilteredProcessorValueTreeModel(treeModel));
/*
* Remember this triple and its index in the big list so we can update
* the map for this enactment after we have finished iterating
*/
int index = listOfPortDataLists.indexOf(portDataList);
tree.getSelectionModel().setSelectionMode(SINGLE_TREE_SELECTION);
tree.setExpandsSelectedPaths(true);
tree.setRootVisible(false);
tree.setShowsRootHandles(true);
tree.setCellRenderer(new ProcessorResultCellRenderer());
// Expand the whole tree
/*
* for (int row = 0; row < tree.getRowCount(); row++) { tree.expandRow(row); }
*/
tree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
TreePath selectionPath = e.getNewLeadSelectionPath();
if (selectionPath != null) {
// Get the selected node
Object selectedNode = selectionPath.getLastPathComponent();
ProcessorPortResultsViewTab selectedResultTab = (ProcessorPortResultsViewTab) tabbedPane
.getSelectedComponent();
selectedResultTab.getRenderedResultComponent().setNode(
(ProcessorResultTreeNode) selectedNode);
}
}
});
portDataList.set(2, tree); // set the new tree
if (index != -1) {
/*
* Put the tree in the map and put the modified list back to the map
*/
listOfPortDataLists.set(index, portDataList);
map.put(enactment, listOfPortDataLists);
}
return tree;
}
private JTree updateTreeForPort(ProcessorPortResultsViewTab selectedTab,
List<Object> portDataList) {
JTree tree = (JTree) portDataList.get(2);
/*
* Show the right value in the rendering component i.e. render the
* selected value for this port and this enactment if anything was
* selected in the result for port tree.
*/
TreePath selectionPath = tree.getSelectionPath();
if (selectionPath != null) {
// Get the selected node
Object selectedNode = selectionPath.getLastPathComponent();
selectedTab.getRenderedResultComponent().setNode(
(ProcessorResultTreeNode) selectedNode);
}
return tree;
}
private void disableResultTabForNode(final ProcessorPortResultsViewTab selectedResultTab,
DefaultMutableTreeNode lastPathComponent) {
selectedResultTab.setResultsTree(null);
String label = labelForNode(lastPathComponent);
iterationLabel.setText(label);
saveAllButton.setEnabled(false);
}
private StringBuilder labelForProcEnactment(ProcessorEnactmentsTreeNode procEnactmentTreeNode,
ProcessorEnactment processorEnactment) {
StringBuilder iterationLabelText = new StringBuilder();
// Use <html> so we can match font metrics of titleJLabel
iterationLabelText.append("<html><body>");
iterationLabelText.append(procEnactmentTreeNode);
Timestamp started = processorEnactment.getEnactmentStarted();
Timestamp ended = processorEnactment.getEnactmentEnded();
if (started != null) {
if (procEnactmentTreeNode.getErrorState() == INPUT_ERRORS)
iterationLabelText.append(" <font color='#cc9700'>skipped</font> ");
else
iterationLabelText.append(" started ");
iterationLabelText.append(ISO_8601.format(started));
}
if (ended != null
&& procEnactmentTreeNode.getErrorState() != INPUT_ERRORS) {
// Don't show End time if there was input errors
if (started != null) {
iterationLabelText.append(", ");
}
if (procEnactmentTreeNode.getErrorState() == OUTPUT_ERRORS)
iterationLabelText.append(" <font color='red'>failed</font> ");
else
iterationLabelText.append(" ended ");
iterationLabelText.append(ISO_8601.format(ended));
if (started != null) {
long duration = ended.getTime() - started.getTime();
iterationLabelText.append(" (");
iterationLabelText.append(formatMilliseconds(duration));
iterationLabelText.append(")");
}
}
iterationLabelText.append("</body></html>");
return iterationLabelText;
}
private String labelForNode(DefaultMutableTreeNode node) {
if (node == null)
return "No selection";
StringBuilder label = new StringBuilder();
label.append(node);
if (node.getUserObject() != null) {
label.append(" containing ");
label.append(node.getLeafCount());
label.append(" iterations");
}
return label.toString();
}
public void populateEnactmentsMaps() {
synchronized (enactmentsGotSoFar) {
// Get processor enactments (invocations) from provenance
// Create the array of nested processors' names
String[] processorNamesPath = null;
if (processorsPath != null) { // should not be null really
processorNamesPath = new String[processorsPath.size()];
int i = 0;
for (Processor proc : processorsPath)
processorNamesPath[i++] = proc.getLocalName();
} else { // This should not really happen!
processorNamesPath = new String[1];
processorNamesPath[0] = processor.getLocalName();
}
List<ProcessorEnactment> processorEnactmentsStack = provenanceAccess
.getProcessorEnactments(runId, processorNamesPath);
if (processorId == null && !processorEnactmentsStack.isEmpty()) {
// Extract processor ID from very first processorEnactment
processorId = processorEnactmentsStack.get(0).getProcessorId();
}
while (!processorEnactmentsStack.isEmpty()) {
// fetch LAST one first, so we'll get the parent's early
ProcessorEnactment processorEnactment = processorEnactmentsStack
.remove(processorEnactmentsStack.size() - 1);
if (!enactmentsGotSoFar.contains(processorEnactment)) {
enactmentsGotSoFar.add(processorEnactment);
enactmentIdsGotSoFar.add(processorEnactment
.getProcessEnactmentId());
String parentId = processorEnactment
.getParentProcessorEnactmentId();
if (parentId != null
&& !enactmentIdsGotSoFar.contains(parentId)) {
/*
* Also add parent (and their parent, etc) - so that we
* can show the full iteration treeenactmentIdsGotSoFar
*/
ProcessorEnactment parentEnactment = provenanceAccess
.getProcessorEnactment(parentId);
if (parentEnactment == null) {
logger.error("Could not find parent processor enactment id="
+ parentId
+ ", skipping "
+ processorEnactment);
enactmentsGotSoFar.remove(processorEnactment);
enactmentIdsGotSoFar.remove(processorEnactment);
continue;
}
processorEnactmentsStack.add(parentEnactment);
}
}
if (!processorEnactment.getProcessorId().equals(processorId))
// A parent processors, no need to fetch their data bindings
continue;
String initialInputs = processorEnactment.getInitialInputsDataBindingId();
String finalOutputs = processorEnactment.getFinalOutputsDataBindingId();
boolean fetchingInputs = initialInputs != null
&& !enactmentsToInputPortData.containsKey(processorEnactment);
boolean fetchingOutputs = finalOutputs != null
&& !enactmentsToOutputPortData.containsKey(processorEnactment);
Map<Port, T2Reference> dataBindings = new HashMap<Port, T2Reference>();
if (fetchingInputs) {
dataBindings = provenanceAccess.getDataBindings(initialInputs);
enactmentsToInputPortData
.put(processorEnactment, new ArrayList<List<Object>>());
}
if (fetchingOutputs) {
enactmentsToOutputPortData.put(processorEnactment,
new ArrayList<List<Object>>());
if (!fetchingInputs
|| (finalOutputs != null && !finalOutputs
.equals(initialInputs)))
dataBindings.putAll(provenanceAccess.getDataBindings(finalOutputs));
}
for (Entry<Port, T2Reference> entry : dataBindings.entrySet()) {
/*
* Create (port, t2Ref, tree) list for this enactment. Tree
* is set to null initially and populated on demand (when
* user clicks on particular enactment/iteration node).
*/
List<Object> dataOnPortList = new ArrayList<>();
Port port = entry.getKey();
dataOnPortList.add(port); // port
T2Reference t2Reference = entry.getValue();
dataOnPortList.add(t2Reference); // t2Ref
/*
* tree (will be populated when a user clicks on this iteration and this port
* tab is selected)
*/
dataOnPortList.add(null);
if (port.isInputPort() && fetchingInputs) { // Input port
if (t2Reference.containsErrors())
enactmentsWithErrorInputs.add(processorEnactment);
List<List<Object>> listOfPortDataLists = enactmentsToInputPortData
.get(processorEnactment);
listOfPortDataLists.add(dataOnPortList);
enactmentsToInputPortData.put(processorEnactment, listOfPortDataLists);
} else if (!port.isInputPort() && fetchingOutputs) { // output port
if (t2Reference.containsErrors())
enactmentsWithErrorOutputs.add(processorEnactment);
List<List<Object>> listOfPortDataLists = enactmentsToOutputPortData
.get(processorEnactment);
listOfPortDataLists.add(dataOnPortList);
enactmentsToOutputPortData.put(processorEnactment, listOfPortDataLists);
}
}
}
}
}
private List<TreePath> expandedPaths = new ArrayList<>();
private TreePath selectionPath = null;
private void rememberPaths() {
expandedPaths.clear();
for (Enumeration<?> e = processorEnactmentsTree
.getExpandedDescendants(new TreePath(filteredTreeModel
.getRoot())); (e != null) && e.hasMoreElements();)
expandedPaths.add((TreePath) e.nextElement());
selectionPath = processorEnactmentsTree.getSelectionPath();
}
private void reinstatePaths() {
for (TreePath path : expandedPaths)
if (filteredTreeModel.isShown((DefaultMutableTreeNode) path
.getLastPathComponent()))
processorEnactmentsTree.expandPath(path);
if (selectionPath != null) {
if (filteredTreeModel
.isShown((DefaultMutableTreeNode) selectionPath
.getLastPathComponent()))
processorEnactmentsTree.setSelectionPath(selectionPath);
else
processorEnactmentsTree.clearSelection();
}
}
public void updateTree() {
rememberPaths();
processorEnactmentsTreeModel.update(enactmentsGotSoFar);
filteredTreeModel.reload();
reinstatePaths();
DefaultMutableTreeNode firstLeaf = ((DefaultMutableTreeNode) filteredTreeModel
.getRoot()).getFirstLeaf();
if ((firstLeaf != null)
&& (processorEnactmentsTree.getPathForRow(0) == null))
processorEnactmentsTree.scrollPathToVisible(new TreePath(
(Object[]) firstLeaf.getPath()));
if (facade == null)
resultsUpdateNeeded = false;
setDataTreeForResultTab();
}
private Runnable updateTreeRunnable = new Runnable() {
@Override
public void run() {
updateTree();
}
};
public void update() {
if (resultsUpdateNeeded) {
IntermediateValuesSwingWorker intermediateValuesSwingWorker = new IntermediateValuesSwingWorker(
this);
IntermediateValuesInProgressDialog dialog = new IntermediateValuesInProgressDialog();
intermediateValuesSwingWorker
.addPropertyChangeListener(new SwingWorkerCompletionWaiter(
dialog));
intermediateValuesSwingWorker.execute();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
if (!intermediateValuesSwingWorker.isDone())
dialog.setVisible(true);
if (intermediateValuesSwingWorker.getException() != null)
logger.error("Populating enactments failed",
intermediateValuesSwingWorker.getException());
else if (isEventDispatchThread())
updateTreeRunnable.run();
else
invokeLater(updateTreeRunnable);
}
}
public void clear() {
tabbedPane.removeAll();
}
public void onDispose() {
}
@Override
protected void finalize() throws Throwable {
onDispose();
}
private class SaveAllAction extends AbstractAction {
// private WorkflowResultsComponent parent;
public SaveAllAction(String name, ProcessorResultsComponent resultViewComponent) {
super(name);
// this.parent = resultViewComponent;
putValue(SMALL_ICON, saveAllIcon);
}
@Override
public void actionPerformed(ActionEvent e) {
ProcessorEnactment processorEnactment = (ProcessorEnactment) procEnactmentTreeNode
.getUserObject();
String initialInputs = processorEnactment.getInitialInputsDataBindingId();
String finalOutputs = processorEnactment.getFinalOutputsDataBindingId();
Map<String, T2Reference> inputBindings = new TreeMap<>();
for (Entry<Port, T2Reference> entry : provenanceAccess
.getDataBindings(initialInputs).entrySet())
inputBindings.put(entry.getKey().getPortName(),
entry.getValue());
Map<String, T2Reference> outputBindings = new TreeMap<>();
for (Entry<Port, T2Reference> entry : provenanceAccess
.getDataBindings(finalOutputs).entrySet())
outputBindings.put(entry.getKey().getPortName(),
entry.getValue());
String title = "Service iteration data saver";
final JDialog dialog = new HelpEnabledDialog(getMainWindow(),
title, true);
dialog.setResizable(false);
dialog.setLocationRelativeTo(getMainWindow());
JPanel panel = new JPanel(new BorderLayout());
DialogTextArea explanation = new DialogTextArea();
explanation
.setText("Select the service input and output ports to save the associated data");
explanation.setColumns(40);
explanation.setEditable(false);
explanation.setOpaque(false);
explanation.setBorder(new EmptyBorder(5, 20, 5, 20));
explanation.setFocusable(false);
explanation.setFont(new JLabel().getFont()); // make the font the same as for other
// components in the dialog
panel.add(explanation, NORTH);
final Map<String, JCheckBox> inputChecks = new HashMap<>();
final Map<String, JCheckBox> outputChecks = new HashMap<>();
final Map<JCheckBox, T2Reference> checkReferences = new HashMap<>();
final Map<String, T2Reference> chosenReferences = new HashMap<>();
final Set<Action> actionSet = new HashSet<Action>();
ItemListener listener = new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
JCheckBox source = (JCheckBox) e.getItemSelectable();
if (inputChecks.containsValue(source)
&& source.isSelected()
&& outputChecks.containsKey(source.getText()))
outputChecks.get(source.getText()).setSelected(false);
if (outputChecks.containsValue(source)
&& source.isSelected()
&& inputChecks.containsKey(source.getText()))
inputChecks.get(source.getText()).setSelected(false);
chosenReferences.clear();
for (JCheckBox checkBox : checkReferences.keySet())
if (checkBox.isSelected())
chosenReferences.put(checkBox.getText(),
checkReferences.get(checkBox));
}
};
JPanel portsPanel = new JPanel(new GridBagLayout());
portsPanel.setBorder(new CompoundBorder(new EmptyBorder(new Insets(
5, 10, 5, 10)), new EtchedBorder(LOWERED)));
if (!inputBindings.isEmpty()) {
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.WEST;
gbc.fill = NONE;
gbc.weightx = 0.0;
gbc.weighty = 0.0;
gbc.insets = new Insets(5, 10, 5, 10);
portsPanel.add(new JLabel("Iteration inputs:"), gbc);
// JPanel inputsPanel = new JPanel();
// WeakHashMap<String, T2Reference> pushedDataMap = null;
TreeMap<String, JCheckBox> sortedBoxes = new TreeMap<>();
for (Entry<String, T2Reference> inputEntry : inputBindings
.entrySet()) {
String portName = inputEntry.getKey();
T2Reference o = inputEntry.getValue();
JCheckBox checkBox = new JCheckBox(portName);
checkBox.setSelected(!outputBindings.containsKey(portName));
checkBox.addItemListener(listener);
inputChecks.put(portName, checkBox);
sortedBoxes.put(portName, checkBox);
checkReferences.put(checkBox, o);
}
gbc.insets = new Insets(0, 10, 0, 10);
for (String portName : sortedBoxes.keySet()) {
gbc.gridy++;
portsPanel.add(sortedBoxes.get(portName), gbc);
}
gbc.gridy++;
gbc.fill = BOTH;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbc.insets = new Insets(5, 10, 5, 10);
portsPanel.add(new JLabel(""), gbc); // empty space
}
if (!outputBindings.isEmpty()) {
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.WEST;
gbc.fill = NONE;
gbc.weightx = 0.0;
gbc.weighty = 0.0;
gbc.insets = new Insets(5, 10, 5, 10);
portsPanel.add(new JLabel("Iteration outputs:"), gbc);
TreeMap<String, JCheckBox> sortedBoxes = new TreeMap<>();
for (Entry<String, T2Reference> outputEntry : outputBindings.entrySet()) {
String portName = outputEntry.getKey();
T2Reference o = outputEntry.getValue();
JCheckBox checkBox = new JCheckBox(portName);
checkBox.setSelected(true);
checkReferences.put(checkBox, o);
checkBox.addItemListener(listener);
outputChecks.put(portName, checkBox);
sortedBoxes.put(portName, checkBox);
}
gbc.insets = new Insets(0, 10, 0, 10);
for (String portName : sortedBoxes.keySet()) {
gbc.gridy++;
portsPanel.add(sortedBoxes.get(portName), gbc);
}
gbc.gridy++;
gbc.fill = BOTH;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbc.insets = new Insets(5, 10, 5, 10);
portsPanel.add(new JLabel(""), gbc); // empty space
}
panel.add(portsPanel, CENTER);
chosenReferences.clear();
for (JCheckBox checkBox : checkReferences.keySet())
if (checkBox.isSelected())
chosenReferences.put(checkBox.getText(),
checkReferences.get(checkBox));
JPanel buttonsBar = new JPanel();
buttonsBar.setLayout(new FlowLayout());
// Get all existing 'Save result' actions
for (SaveAllResultsSPI spi : saveActions) {
AbstractAction action = spi.getAction();
actionSet.add(action);
JButton saveButton = new JButton((AbstractAction) action);
if (action instanceof SaveAllResultsSPI) {
// ((SaveAllResultsSPI) action).setChosenReferences(chosenReferences);
((SaveAllResultsSPI) action).setParent(dialog);
}
// saveButton.setEnabled(true);
buttonsBar.add(saveButton);
}
JButton cancelButton = new JButton("Cancel", closeIcon);
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dialog.setVisible(false);
}
});
buttonsBar.add(cancelButton);
panel.add(buttonsBar, SOUTH);
panel.revalidate();
dialog.add(panel);
dialog.pack();
dialog.setVisible(true);
}
}
}