blob: 88efb1a3ada10052393163311a5ddf747b79e6cc [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2008 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.loop;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import javax.swing.AbstractAction;
import javax.swing.Box;
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.JTextField;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import net.sf.taverna.t2.activities.beanshell.views.BeanshellConfigurationPanel;
import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
import net.sf.taverna.t2.workbench.loop.comparisons.Comparison;
import net.sf.taverna.t2.workbench.ui.Utils;
import org.apache.log4j.Logger;
import uk.org.taverna.configuration.app.ApplicationConfiguration;
import uk.org.taverna.scufl2.api.activity.Activity;
import uk.org.taverna.scufl2.api.common.Scufl2Tools;
import uk.org.taverna.scufl2.api.configurations.Configuration;
import uk.org.taverna.scufl2.api.core.Processor;
import uk.org.taverna.scufl2.api.profiles.Profile;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* UI for {@link LoopConfiguration}
*
* @author Stian Soiland-Reyes
*
*/
@SuppressWarnings("serial")
public class LoopConfigurationPanel extends JPanel {
private static final String CONDITION_ACTIVITY = "conditionActivity";
private static final String DEFAULT_DELAY_S = "0.5";
protected ObjectNode configuration;
private static final Scufl2Tools scufl2tools = new Scufl2Tools();
private ApplicationConfiguration applicationConfig;
protected final Processor processor;
protected JPanel headerPanel = new JPanel();
protected JPanel optionsPanel = new JPanel();
protected JPanel configPanel = new JPanel();
protected JPanel customPanel = new JPanel();
protected JLabel valueTypeLabel = new JLabel("the string");
protected JTextField valueField = new JTextField("", 15);
protected JLabel delayLabel = new JLabel("adding a delay of ");
protected JTextField delayField = new JTextField(
Double.toString(ActivityGenerator.DEFAULT_DELAY_S), 4);
protected JLabel secondsLabel = new JLabel(" seconds between the loops.");
private JComboBox<String> portCombo;
private JComboBox<Comparison> comparisonCombo;
private JButton customizeButton;
protected ObjectNode loopLayer;
private Object Comparison;
private Activity originalCondition = null;
private Profile profile;
public LoopConfigurationPanel(Processor processor, ObjectNode loopLayer,
Profile profile, ApplicationConfiguration applicationConfig) {
this.processor = processor;
this.loopLayer = loopLayer;
this.profile = profile;
this.applicationConfig = applicationConfig;
this.setBorder(new EmptyBorder(10,10,10,10));
initialise();
setConfiguration(loopLayer);
}
public ObjectNode getConfiguration() {
uiToConfig();
return loopLayer.deepCopy();
}
private static Logger logger = Logger
.getLogger(LoopConfigurationPanel.class);
protected void uiToConfig() {
String comparisonStr = configuration.path(ActivityGenerator.COMPARISON).asText();
if (comparisonStr.isEmpty()) {
comparisonStr = ActivityGenerator.CUSTOM_COMPARISON;
}
if (comparisonStr.equals(ActivityGenerator.CUSTOM_COMPARISON)
&& ! configuration.path(CONDITION_ACTIVITY).asText().isEmpty()) {
// Ignore values
} else {
configuration.put("runFirst", true);
if (portCombo.getSelectedItem() == null) {
// unconfigured port
configuration.remove(ActivityGenerator.COMPARE_PORT);
configuration.putNull(CONDITION_ACTIVITY);
return;
} else {
configuration.put(ActivityGenerator.COMPARE_PORT,
((String) portCombo.getSelectedItem()));
}
Comparison comparison = (Comparison) comparisonCombo
.getSelectedItem();
if (comparison == null) {
configuration.remove(ActivityGenerator.COMPARISON);
configuration.putNull(CONDITION_ACTIVITY);
return;
} else {
configuration
.put(ActivityGenerator.COMPARISON, comparison.getId());
}
configuration.put(ActivityGenerator.COMPARE_VALUE, valueField
.getText());
configuration.put(ActivityGenerator.DELAY, Double.parseDouble(delayField.getText()));
configuration.put(ActivityGenerator.IS_FEED_BACK, feedBackCheck.isSelected());
// Generate activity
ActivityGenerator activityGenerator = new ActivityGenerator(
configuration, processor);
configuration.put(CONDITION_ACTIVITY, activityGenerator.generateActivity().getName());
}
}
public class ResetAction extends AbstractAction {
public ResetAction() {
super("Clear");
}
public void actionPerformed(ActionEvent e) {
configuration.putNull(CONDITION_ACTIVITY);
configToUi();
}
}
private final class CustomizeAction implements ActionListener {
// public CustomizeAction() {
// super();
// //putValue(NAME, "Customise loop condition");
// }
public void actionPerformed(ActionEvent e) {
uiToConfig();
String conditionName = configuration.path(CONDITION_ACTIVITY).asText();
Activity condition = profile.getActivities().getByName(conditionName);
if (condition == null) {
condition = new Activity();
profile.getActivities().add(condition);
configuration.put(CONDITION_ACTIVITY, condition.getName());
condition.setType(ActivityGenerator.BEANSHELL_ACTIVITY);
Configuration config = scufl2tools.createConfigurationFor(condition, ActivityGenerator.BEANSHELL_CONFIG);
} else if (!(condition.getType().equals(ActivityGenerator.BEANSHELL_ACTIVITY))) {
logger.warn("Can't configure unsupported loop condition of service type "
+ condition.getType());
return;
}
Frame owner = Utils.getParentFrame(LoopConfigurationPanel.this);
final BeanshellConfigurationPanel beanshellConfigView = new BeanshellConfigurationPanel(
condition, applicationConfig);
final JDialog dialog = new HelpEnabledDialog(owner, "Customize looping", true);
dialog.setLayout(new BorderLayout());
dialog.add(beanshellConfigView, BorderLayout.NORTH);
dialog.setSize(600, 600);
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
JButton applyButton = new JButton(new AbstractAction() {
public void actionPerformed(ActionEvent e) {
if (beanshellConfigView.isConfigurationChanged()) {
beanshellConfigView.noteConfiguration();
// beanshellActivity.configure(beanshellConfigView
// .getConfiguration());
// configuration.setCondition(beanshellActivity);
Configuration config = beanshellConfigView.getConfiguration();
// TODO: Do we need to store this somehow?
configuration.put(
ActivityGenerator.COMPARISON,
ActivityGenerator.CUSTOM_COMPARISON);
}
dialog.setVisible(false);
configToUi();
}
});
applyButton.setText("Apply");
buttonPanel.add(applyButton);
JButton closeButton = new JButton(new AbstractAction() {
public void actionPerformed(ActionEvent e) {
dialog.setVisible(false);
}
});
closeButton.setText("Cancel");
buttonPanel.add(closeButton);
dialog.add(buttonPanel, BorderLayout.SOUTH);
dialog.setLocationRelativeTo(customizeButton);
dialog.setVisible(true);
}
}
public void setConfiguration(ObjectNode configuration) {
this.configuration = configuration.deepCopy();
configToUi();
}
protected void configToUi() {
String comparisonId;
if (configuration.has(ActivityGenerator.COMPARISON)) {
comparisonId = configuration.get(ActivityGenerator.COMPARISON)
.asText();
} else {
comparisonId = ActivityGenerator.CUSTOM_COMPARISON;
}
if (comparisonId.equals(ActivityGenerator.CUSTOM_COMPARISON)
&& configuration.has("conditionalActivity")) {
configPanel.setVisible(false);
customPanel.setVisible(true);
} else {
configPanel.setVisible(true);
customPanel.setVisible(false);
}
portCombo.setSelectedItem(configuration.get(ActivityGenerator.COMPARE_PORT).asText());
if (portCombo.getSelectedIndex() == -1
&& portCombo.getModel().getSize() > 0) {
portCombo.setSelectedIndex(0);
}
Comparison comparison = ActivityGenerator
.getComparisonById(comparisonId);
comparisonCombo.setSelectedItem(comparison);
if (comparisonCombo.getSelectedIndex() == -1
&& comparisonCombo.getModel().getSize() > 0) {
comparisonCombo.setSelectedIndex(0);
}
valueField.setText(configuration.get(ActivityGenerator.COMPARE_VALUE).asText());
if (configuration.has(ActivityGenerator.DELAY)) {
delayField.setText(configuration.get(ActivityGenerator.DELAY).asText());
} else {
delayField.setText(DEFAULT_DELAY_S);
}
feedBackCheck.setSelected(configuration.get(ActivityGenerator.IS_FEED_BACK).asBoolean());
updateFeedbackHelp();
}
private void initialise() {
removeAll();
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.anchor = GridBagConstraints.FIRST_LINE_START;
gbc.gridx = 0;
gbc.weightx = 0.1;
makeHeader();
add(headerPanel, gbc);
makeConfigPanel();
gbc.weighty = 0.1;
gbc.anchor = GridBagConstraints.CENTER;
gbc.fill = GridBagConstraints.BOTH;
add(configPanel, gbc);
makeCustomPanel();
add(customPanel, gbc);
makeOptions();
add(optionsPanel, gbc);
}
protected void makeCustomPanel() {
customPanel.removeAll();
customPanel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.LINE_START;
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = 2;
gbc.weightx = 0.1;
gbc.fill = GridBagConstraints.HORIZONTAL;
JLabel helpLabel = new JLabel(
"<html><body>"
+ "The service <strong>" + processor.getName() + "</strong> will be "
+ "invoked repeatedly as "
+ "long as the <em>customized loop condition service</em> returns a string equal "
+ "to <strong>\"true\"</strong> on its output port <code>loop</code>."
// + "<br><br>"
// + "Input ports of the condition service will be populated with values from "
// + "the <em>corresponding output ports</em> of the main service invocation "
// + "(as long as they are also "
// + "<strong>connected</strong> in the containing workflow)."
// + "<br><br> "
//
// + "Any <em>matching "
// + "output ports</em> from the condition service will provide the corresponding "
// + "<em>inputs</em> to the main service while looping. You will need to connect "
// + "the <em>initial inputs</em> in the containing workflow."
+ "</body></html>");
customPanel.add(helpLabel, gbc);
gbc.weightx = 0.1;
gbc.fill = GridBagConstraints.NONE;
gbc.gridx = 0;
gbc.gridy++;
gbc.gridwidth = 1;
gbc.anchor = GridBagConstraints.EAST;
JPanel customiseButtonPanel = new JPanel(new FlowLayout());
customiseButtonPanel.setBorder(new EmptyBorder(10,0,0,0));
customizeButton = new JButton("Customize loop condition");
customizeButton.addActionListener(new CustomizeAction());
customiseButtonPanel.add(customizeButton);
customiseButtonPanel.add(new JButton(new ResetAction()));
customPanel.add(customiseButtonPanel, gbc);
}
protected void makeConfigPanel() {
configPanel.removeAll();
configPanel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.LINE_START;
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = 4;
gbc.weightx = 0.1;
gbc.fill = GridBagConstraints.HORIZONTAL;
JLabel invokedRepeatedlyLabel = new JLabel(
"<html><body>The service <strong>" + processor.getName() + "</strong> " +
"will be invoked repeatedly <em>until</em> its output port</body></html>");
invokedRepeatedlyLabel.setBorder(new EmptyBorder(10,0,10,0)); // give some top and bottom border to the label
configPanel.add(invokedRepeatedlyLabel, gbc);
gbc.ipadx = 4;
gbc.ipady = 4;
gbc.weightx = 0.0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridx = 0;
gbc.gridy = 1;
gbc.gridwidth = 1;
List<String> activityOutputPorts = getActivityOutputPorts();
portCombo = new JComboBox(activityOutputPorts.toArray());
configPanel.add(portCombo, gbc);
comparisonCombo = new JComboBox(ActivityGenerator.comparisons.toArray());
comparisonCombo.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Comparison selectedComparison = (Comparison) comparisonCombo
.getSelectedItem();
if (selectedComparison != null) {
valueTypeLabel.setText("the "
+ selectedComparison.getValueType());
}
}
});
if (comparisonCombo.getSelectedIndex() == -1) {
comparisonCombo.setSelectedIndex(0);
}
gbc.gridx = 1;
gbc.gridy = 1;
configPanel.add(comparisonCombo, gbc);
gbc.gridx = 2;
gbc.gridy = 1;
valueTypeLabel.setHorizontalAlignment(SwingConstants.RIGHT);
configPanel.add(valueTypeLabel, gbc);
gbc.gridx = 3;
gbc.gridy = 1;
gbc.weightx = 0.5; // request all extra space
gbc.fill = GridBagConstraints.HORIZONTAL;
configPanel.add(valueField, gbc);
gbc.gridx = 0;
gbc.gridy = 2;
gbc.weightx = 0.0;
configPanel.add(delayLabel, gbc);
gbc.gridx = 1;
gbc.gridx = 1;
gbc.gridy = 2;
gbc.weightx = 0.0;
delayField.setHorizontalAlignment(JTextField.RIGHT);
configPanel.add(delayField, gbc);
gbc.gridx = 2;
gbc.gridy = 2;
gbc.gridwidth = 2;
gbc.weightx = 0.5; // request all extra space
gbc.fill = GridBagConstraints.HORIZONTAL;
configPanel.add(secondsLabel, gbc);
if (activityOutputPorts.isEmpty()) {
JLabel warningLabel = new JLabel(
"<html><body><strong>Warning:</strong><br>"
+ "<i>No single value output ports detected on the main service, "
+ "cannot use built-in comparisons. You may still add a customized " +
"looping script</i></body></html>");
gbc.gridx = 0;
gbc.gridy++;
gbc.gridwidth = 4;
gbc.weightx = 0.1;
gbc.fill = GridBagConstraints.BOTH;
gbc.gridy++;
configPanel.add(warningLabel, gbc);
invokedRepeatedlyLabel.setVisible(false);
portCombo.setVisible(false);
comparisonCombo.setVisible(false);
portWarning.setVisible(false);
valueTypeLabel.setVisible(false);
valueField.setVisible(false);
delayField.setVisible(false);
delayLabel.setVisible(false);
secondsLabel.setVisible(false);
}
gbc.gridy++;
gbc.gridx = 0;
gbc.weightx = 0.1;
gbc.gridwidth = 4;
gbc.weightx = 0.1;
gbc.fill = GridBagConstraints.BOTH;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(10, 0, 10, 0);
configPanel.add(portWarning, gbc);
gbc.insets = new Insets(0, 0, 0, 0);
gbc.weightx = 0.1;
gbc.fill = GridBagConstraints.NONE;
gbc.gridx = 0;
gbc.gridy++;
gbc.gridwidth = 4;
gbc.anchor = GridBagConstraints.LAST_LINE_END;
JPanel customiseButtonPanel = new JPanel(new FlowLayout());
customizeButton = new JButton("Customize loop condition");
customizeButton.addActionListener(new CustomizeAction());
customiseButtonPanel.add(customizeButton);
configPanel.add(customiseButtonPanel, gbc);
// filler
gbc.gridy++;
gbc.fill = GridBagConstraints.BOTH;
gbc.gridx = 4;
gbc.weightx = 0.1;
gbc.weighty = 0.1;
gbc.gridwidth = 4;
configPanel.add(Box.createGlue(), gbc);
}
private List<String> getActivityOutputPorts() {
// Should already be sorted
return new ArrayList<>(processor.getOutputPorts().getNames());
}
protected JCheckBox feedBackCheck = new JCheckBox(
"Enable output port to input port feedback");
private JLabel portWarning = new JLabel(
"<html><body><small>Note that for Taverna to be able to execute this loop, "
+ "the output port <strong>must</strong> be connected to an input of another service "
+ "or a workflow output port.</small></body></html>");
protected void makeOptions() {
optionsPanel.removeAll();
optionsPanel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 0.1;
gbc.anchor = GridBagConstraints.FIRST_LINE_START;
gbc.fill = GridBagConstraints.HORIZONTAL;
feedBackCheck.setBorder(new EmptyBorder(0,0,10,0));
optionsPanel.add(feedBackCheck, gbc);
feedBackCheck.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
updateFeedbackHelp();
}
});
updateFeedbackHelp();
gbc.gridy = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
optionsPanel.add(feedbackHelp, gbc);
}
protected void updateFeedbackHelp() {
feedbackHelp.setEnabled(feedBackCheck.isSelected());
Color color;
if (feedBackCheck.isSelected()) {
color = valueTypeLabel.getForeground();
} else {
// Work around
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4303706
// and assume gray is the 'disabled' colour in our Look n Feel
color = Color.gray;
}
feedbackHelp.setForeground(color);
}
JLabel feedbackHelp = new JLabel(
"<html><small>"
+ "<p>When feedback is enabled, the value of the output port is used as input " +
"the next time the loop in invoked. The input and output ports used for feedback "
+ "<strong>must</strong> have the same <strong>name</strong> and <strong>depth</strong>."
+ "</p><br>"
+ "<p>Feedback can be useful for looping over a nested workflow, "
+ "where the nested workflow's output determines its next input value.</p><br>"
+ "<p>In order to use feedback looping, you must provide an initial value to the input port by "
+ "connecting it to the output of a previous service or workflow input port."
+ "The output port used as feedback also has to be connected to a downstream service " +
"or a workflow output port.</p>"
+ "</small></html>");
protected void makeHeader() {
headerPanel.removeAll();
headerPanel.setLayout(new BorderLayout());
//headerPanel.add(new ShadedLabel("Looping for service"
// + processor.getLocalName(), ShadedLabel.ORANGE));
}
}